Merge "Fingerprint Introduction FragmentActivity"
This commit is contained in:
committed by
Android (Google) Code Review
commit
148774d287
@@ -2403,6 +2403,11 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".biometrics2.ui.view.FingerprintEnrollmentActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.MANAGE_FINGERPRINT"
|
||||||
|
android:theme="@style/GlifTheme.Light"/>
|
||||||
|
|
||||||
<activity android:name=".biometrics.fingerprint.FingerprintEnrollIntroductionInternal"
|
<activity android:name=".biometrics.fingerprint.FingerprintEnrollIntroductionInternal"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/GlifTheme.Light"
|
android:theme="@style/GlifTheme.Light"
|
||||||
|
6
res/layout/biometric_enrollment_container.xml
Normal file
6
res/layout/biometric_enrollment_container.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/fragment_container_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
@@ -39,6 +39,7 @@ import com.android.settings.SubSettings;
|
|||||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
|
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
|
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal;
|
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal;
|
||||||
|
import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity;
|
||||||
import com.android.settings.core.FeatureFlags;
|
import com.android.settings.core.FeatureFlags;
|
||||||
import com.android.settings.homepage.DeepLinkHomepageActivity;
|
import com.android.settings.homepage.DeepLinkHomepageActivity;
|
||||||
import com.android.settings.homepage.DeepLinkHomepageActivityInternal;
|
import com.android.settings.homepage.DeepLinkHomepageActivityInternal;
|
||||||
@@ -225,6 +226,7 @@ public class ActivityEmbeddingRulesController {
|
|||||||
.buildSearchIntent(mContext, SettingsEnums.SETTINGS_HOMEPAGE);
|
.buildSearchIntent(mContext, SettingsEnums.SETTINGS_HOMEPAGE);
|
||||||
addActivityFilter(activityFilters, searchIntent);
|
addActivityFilter(activityFilters, searchIntent);
|
||||||
}
|
}
|
||||||
|
addActivityFilter(activityFilters, FingerprintEnrollmentActivity.class);
|
||||||
addActivityFilter(activityFilters, FingerprintEnrollIntroduction.class);
|
addActivityFilter(activityFilters, FingerprintEnrollIntroduction.class);
|
||||||
addActivityFilter(activityFilters, FingerprintEnrollIntroductionInternal.class);
|
addActivityFilter(activityFilters, FingerprintEnrollIntroductionInternal.class);
|
||||||
addActivityFilter(activityFilters, FingerprintEnrollEnrolling.class);
|
addActivityFilter(activityFilters, FingerprintEnrollEnrolling.class);
|
||||||
|
@@ -25,6 +25,7 @@ import android.content.IntentSender;
|
|||||||
import android.hardware.biometrics.SensorProperties;
|
import android.hardware.biometrics.SensorProperties;
|
||||||
import android.hardware.face.FaceManager;
|
import android.hardware.face.FaceManager;
|
||||||
import android.hardware.face.FaceSensorPropertiesInternal;
|
import android.hardware.face.FaceSensorPropertiesInternal;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.storage.StorageManager;
|
import android.os.storage.StorageManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
@@ -145,6 +146,31 @@ public class BiometricUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param context caller's context
|
||||||
|
* @param isSuw if it is running in setup wizard flows
|
||||||
|
* @param suwExtras setup wizard extras for new intent
|
||||||
|
* @return Intent for starting ChooseLock*
|
||||||
|
*/
|
||||||
|
public static Intent getChooseLockIntent(@NonNull Context context,
|
||||||
|
boolean isSuw, @NonNull Bundle suwExtras) {
|
||||||
|
if (isSuw) {
|
||||||
|
// Default to PIN lock in setup wizard
|
||||||
|
Intent intent = new Intent(context, SetupChooseLockGeneric.class);
|
||||||
|
if (StorageManager.isFileEncrypted()) {
|
||||||
|
intent.putExtra(
|
||||||
|
LockPatternUtils.PASSWORD_TYPE_KEY,
|
||||||
|
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
|
||||||
|
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment
|
||||||
|
.EXTRA_SHOW_OPTIONS_BUTTON, true);
|
||||||
|
}
|
||||||
|
intent.putExtras(suwExtras);
|
||||||
|
return intent;
|
||||||
|
} else {
|
||||||
|
return new Intent(context, ChooseLockGeneric.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param context caller's context
|
* @param context caller's context
|
||||||
* @param activityIntent The intent that started the caller's activity
|
* @param activityIntent The intent that started the caller's activity
|
||||||
|
@@ -32,7 +32,7 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu
|
|||||||
/**
|
/**
|
||||||
* Returns the number of fingerprint enrolled.
|
* Returns the number of fingerprint enrolled.
|
||||||
*/
|
*/
|
||||||
private static final String EXTRA_FINGERPRINT_ENROLLED_COUNT = "fingerprint_enrolled_count";
|
public static final String EXTRA_FINGERPRINT_ENROLLED_COUNT = "fingerprint_enrolled_count";
|
||||||
|
|
||||||
private static final String KEY_LOCK_SCREEN_PRESENT = "wasLockScreenPresent";
|
private static final String KEY_LOCK_SCREEN_PRESENT = "wasLockScreenPresent";
|
||||||
|
|
||||||
|
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* 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.data.repository;
|
||||||
|
|
||||||
|
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
|
||||||
|
|
||||||
|
import android.app.admin.DevicePolicyManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.hardware.fingerprint.Fingerprint;
|
||||||
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
|
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.biometrics.ParentalControlsUtils;
|
||||||
|
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This repository is used to call all APIs in {@link FingerprintManager}
|
||||||
|
*/
|
||||||
|
public class FingerprintRepository {
|
||||||
|
|
||||||
|
@NonNull private final FingerprintManager mFingerprintManager;
|
||||||
|
|
||||||
|
public FingerprintRepository(@NonNull FingerprintManager fingerprintManager) {
|
||||||
|
mFingerprintManager = fingerprintManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first sensor type is UDFPS sensor or not
|
||||||
|
*/
|
||||||
|
public boolean canAssumeUdfps() {
|
||||||
|
FingerprintSensorPropertiesInternal prop = getFirstFingerprintSensorPropertiesInternal();
|
||||||
|
return prop != null && prop.isAnyUdfpsType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get max possible number of fingerprints for a user
|
||||||
|
*/
|
||||||
|
public int getMaxFingerprints() {
|
||||||
|
FingerprintSensorPropertiesInternal prop = getFirstFingerprintSensorPropertiesInternal();
|
||||||
|
return prop != null ? prop.maxEnrollmentsPerUser : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get number of fingerprints that this user enrolled.
|
||||||
|
*/
|
||||||
|
public int getNumOfEnrolledFingerprintsSize(int userId) {
|
||||||
|
final List<Fingerprint> list = mFingerprintManager.getEnrolledFingerprints(userId);
|
||||||
|
return list != null ? list.size() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get maximum possible fingerprints in setup wizard flow
|
||||||
|
*/
|
||||||
|
public int getMaxFingerprintsInSuw(@NonNull Resources resources) {
|
||||||
|
return resources.getInteger(R.integer.suw_max_fingerprints_enrollable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private FingerprintSensorPropertiesInternal getFirstFingerprintSensorPropertiesInternal() {
|
||||||
|
final List<FingerprintSensorPropertiesInternal> props =
|
||||||
|
mFingerprintManager.getSensorPropertiesInternal();
|
||||||
|
return props.size() > 0 ? props.get(0) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call FingerprintManager to generate challenge for first sensor
|
||||||
|
*/
|
||||||
|
public void generateChallenge(int userId,
|
||||||
|
@NonNull FingerprintManager.GenerateChallengeCallback callback) {
|
||||||
|
mFingerprintManager.generateChallenge(userId, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get parental consent required or not during enrollment process
|
||||||
|
*/
|
||||||
|
public boolean isParentalConsentRequired(@NonNull Context context) {
|
||||||
|
return ParentalControlsUtils.parentConsentRequired(context, TYPE_FINGERPRINT) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fingerprint is disable by admin or not
|
||||||
|
*/
|
||||||
|
public boolean isDisabledByAdmin(@NonNull Context context, int userId) {
|
||||||
|
return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
|
||||||
|
context, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, userId) != null;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.biometrics2.factory;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.app.admin.DevicePolicyManager;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentFactory;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import com.android.settings.biometrics2.ui.view.FingerprintEnrollIntroFragment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment factory for biometrics
|
||||||
|
*/
|
||||||
|
public class BiometricsFragmentFactory extends FragmentFactory {
|
||||||
|
|
||||||
|
private final Application mApplication;
|
||||||
|
private final ViewModelProvider mViewModelProvider;
|
||||||
|
|
||||||
|
public BiometricsFragmentFactory(Application application,
|
||||||
|
ViewModelProvider viewModelProvider) {
|
||||||
|
mApplication = application;
|
||||||
|
mViewModelProvider = viewModelProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
|
||||||
|
final Class<? extends Fragment> clazz = loadFragmentClass(classLoader, className);
|
||||||
|
if (FingerprintEnrollIntroFragment.class.equals(clazz)) {
|
||||||
|
final DevicePolicyManager devicePolicyManager =
|
||||||
|
mApplication.getSystemService(DevicePolicyManager.class);
|
||||||
|
if (devicePolicyManager != null) {
|
||||||
|
return new FingerprintEnrollIntroFragment(mViewModelProvider,
|
||||||
|
devicePolicyManager.getResources());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.instantiate(classLoader, className);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.biometrics2.factory;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for BiometricsRepositoryProvider
|
||||||
|
*/
|
||||||
|
public interface BiometricsRepositoryProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get FingerprintRepository
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
FingerprintRepository getFingerprintRepository(@NonNull Application application);
|
||||||
|
}
|
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.biometrics2.factory;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.settings.Utils;
|
||||||
|
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation for BiometricsRepositoryProvider
|
||||||
|
*/
|
||||||
|
public class BiometricsRepositoryProviderImpl implements BiometricsRepositoryProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get FingerprintRepository
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public FingerprintRepository getFingerprintRepository(@NonNull Application application) {
|
||||||
|
final FingerprintManager fingerprintManager =
|
||||||
|
Utils.getFingerprintManagerOrNull(application);
|
||||||
|
if (fingerprintManager == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new FingerprintRepository(fingerprintManager);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.biometrics2.factory;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.app.KeyguardManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory;
|
||||||
|
import androidx.lifecycle.viewmodel.CreationExtras;
|
||||||
|
|
||||||
|
import com.android.internal.widget.LockPatternUtils;
|
||||||
|
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||||
|
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel;
|
||||||
|
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator;
|
||||||
|
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel;
|
||||||
|
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel;
|
||||||
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View model factory for biometric enrollment fragment
|
||||||
|
*/
|
||||||
|
public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
private static final String TAG = "BiometricsViewModelFact";
|
||||||
|
|
||||||
|
public static final CreationExtras.Key<ChallengeGenerator> CHALLENGE_GENERATOR =
|
||||||
|
new CreationExtras.Key<>() {};
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends ViewModel> T create(@NonNull Class<T> modelClass,
|
||||||
|
@NonNull CreationExtras extras) {
|
||||||
|
final Application application = extras.get(AndroidViewModelFactory.APPLICATION_KEY);
|
||||||
|
|
||||||
|
if (application == null) {
|
||||||
|
Log.w(TAG, "create, null application");
|
||||||
|
return create(modelClass);
|
||||||
|
}
|
||||||
|
final FeatureFactory featureFactory = FeatureFactory.getFactory(application);
|
||||||
|
final BiometricsRepositoryProvider provider = FeatureFactory.getFactory(application)
|
||||||
|
.getBiometricsRepositoryProvider();
|
||||||
|
|
||||||
|
if (modelClass.isAssignableFrom(FingerprintEnrollIntroViewModel.class)) {
|
||||||
|
final FingerprintRepository repository = provider.getFingerprintRepository(application);
|
||||||
|
if (repository != null) {
|
||||||
|
return (T) new FingerprintEnrollIntroViewModel(application, repository);
|
||||||
|
}
|
||||||
|
} else if (modelClass.isAssignableFrom(FingerprintEnrollmentViewModel.class)) {
|
||||||
|
final FingerprintRepository repository = provider.getFingerprintRepository(application);
|
||||||
|
if (repository != null) {
|
||||||
|
return (T) new FingerprintEnrollmentViewModel(application, repository,
|
||||||
|
application.getSystemService(KeyguardManager.class));
|
||||||
|
}
|
||||||
|
} else 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return create(modelClass);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE;
|
||||||
|
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_SENSOR_ID;
|
||||||
|
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN;
|
||||||
|
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Secret credential data including
|
||||||
|
* 1. userId
|
||||||
|
* 2. sensorId
|
||||||
|
* 3. challenge
|
||||||
|
* 4. token
|
||||||
|
* 5. gkPwHandle
|
||||||
|
*/
|
||||||
|
public final class CredentialModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default value for an invalid challenge
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
public static final long INVALID_CHALLENGE = -1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default value if GkPwHandle is invalid.
|
||||||
|
*/
|
||||||
|
public static final long INVALID_GK_PW_HANDLE = 0L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default value for a invalid sensor id
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
public static final int INVALID_SENSOR_ID = -1;
|
||||||
|
|
||||||
|
private final Clock mClock;
|
||||||
|
|
||||||
|
private final long mInitMillis;
|
||||||
|
|
||||||
|
private final int mUserId;
|
||||||
|
|
||||||
|
private int mSensorId;
|
||||||
|
@Nullable
|
||||||
|
private Long mUpdateSensorIdMillis = null;
|
||||||
|
|
||||||
|
private long mChallenge;
|
||||||
|
@Nullable
|
||||||
|
private Long mUpdateChallengeMillis = null;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private byte[] mToken;
|
||||||
|
@Nullable
|
||||||
|
private Long mUpdateTokenMillis = null;
|
||||||
|
|
||||||
|
private long mGkPwHandle;
|
||||||
|
@Nullable
|
||||||
|
private Long mClearGkPwHandleMillis = null;
|
||||||
|
|
||||||
|
public CredentialModel(@NonNull Intent intent, @NonNull Clock clock) {
|
||||||
|
mUserId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
|
||||||
|
mSensorId = intent.getIntExtra(EXTRA_KEY_SENSOR_ID, INVALID_SENSOR_ID);
|
||||||
|
mChallenge = intent.getLongExtra(EXTRA_KEY_CHALLENGE, INVALID_CHALLENGE);
|
||||||
|
mToken = intent.getByteArrayExtra(EXTRA_KEY_CHALLENGE_TOKEN);
|
||||||
|
mGkPwHandle = intent.getLongExtra(EXTRA_KEY_GK_PW_HANDLE,
|
||||||
|
INVALID_GK_PW_HANDLE);
|
||||||
|
mClock = clock;
|
||||||
|
mInitMillis = mClock.millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get userId for this credential
|
||||||
|
*/
|
||||||
|
public int getUserId() {
|
||||||
|
return mUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check user id is valid or not
|
||||||
|
*/
|
||||||
|
public static boolean isValidUserId(int userId) {
|
||||||
|
return userId != UserHandle.USER_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get challenge
|
||||||
|
*/
|
||||||
|
public long getChallenge() {
|
||||||
|
return mChallenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set challenge
|
||||||
|
*/
|
||||||
|
public void setChallenge(long value) {
|
||||||
|
mUpdateChallengeMillis = mClock.millis();
|
||||||
|
mChallenge = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get challenge token
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public byte[] getToken() {
|
||||||
|
return mToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set challenge token
|
||||||
|
*/
|
||||||
|
public void setToken(@Nullable byte[] value) {
|
||||||
|
mUpdateTokenMillis = mClock.millis();
|
||||||
|
mToken = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check challengeToken is valid or not
|
||||||
|
*/
|
||||||
|
public static boolean isValidToken(@Nullable byte[] token) {
|
||||||
|
return token != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get gatekeeper password handle
|
||||||
|
*/
|
||||||
|
public long getGkPwHandle() {
|
||||||
|
return mGkPwHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear gatekeeper password handle data
|
||||||
|
*/
|
||||||
|
public void clearGkPwHandle() {
|
||||||
|
mClearGkPwHandleMillis = mClock.millis();
|
||||||
|
mGkPwHandle = INVALID_GK_PW_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check gkPwHandle is valid or not
|
||||||
|
*/
|
||||||
|
public static boolean isValidGkPwHandle(long gkPwHandle) {
|
||||||
|
return gkPwHandle != INVALID_GK_PW_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sensor id
|
||||||
|
*/
|
||||||
|
public int getSensorId() {
|
||||||
|
return mSensorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set sensor id
|
||||||
|
*/
|
||||||
|
public void setSensorId(int value) {
|
||||||
|
mUpdateSensorIdMillis = mClock.millis();
|
||||||
|
mSensorId = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of the object
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final int gkPwHandleLen = ("" + mGkPwHandle).length();
|
||||||
|
final int tokenLen = mToken == null ? 0 : mToken.length;
|
||||||
|
final int challengeLen = ("" + mChallenge).length();
|
||||||
|
return getClass().getSimpleName() + ":{initMillis:" + mInitMillis
|
||||||
|
+ ", userId:" + mUserId
|
||||||
|
+ ", challenge:{len:" + challengeLen
|
||||||
|
+ ", updateMillis:" + mUpdateChallengeMillis + "}"
|
||||||
|
+ ", token:{len:" + tokenLen + ", isValid:" + isValidToken(mToken)
|
||||||
|
+ ", updateMillis:" + mUpdateTokenMillis + "}"
|
||||||
|
+ ", gkPwHandle:{len:" + gkPwHandleLen + ", isValid:"
|
||||||
|
+ isValidGkPwHandle(mGkPwHandle) + ", clearMillis:" + mClearGkPwHandleMillis + "}"
|
||||||
|
+ ", mSensorId:{id:" + mSensorId + ", updateMillis:" + mUpdateSensorIdMillis + "}"
|
||||||
|
+ " }";
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY;
|
||||||
|
|
||||||
|
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.android.settings.SetupWizardUtils;
|
||||||
|
|
||||||
|
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Biometric enrollment generic intent data, which includes
|
||||||
|
* 1. isSuw
|
||||||
|
* 2. isAfterSuwOrSuwSuggestedAction
|
||||||
|
* 3. theme
|
||||||
|
* 4. isFromSettingsSummery
|
||||||
|
* 5. a helper method, getSetupWizardExtras
|
||||||
|
*/
|
||||||
|
public final class EnrollmentRequest {
|
||||||
|
|
||||||
|
private final boolean mIsSuw;
|
||||||
|
private final boolean mIsAfterSuwOrSuwSuggestedAction;
|
||||||
|
private final boolean mIsFromSettingsSummery;
|
||||||
|
private final int mTheme;
|
||||||
|
private final Bundle mSuwExtras;
|
||||||
|
|
||||||
|
public EnrollmentRequest(@NonNull Intent intent, @NonNull Context context) {
|
||||||
|
mIsSuw = WizardManagerHelper.isAnySetupWizard(intent);
|
||||||
|
mIsAfterSuwOrSuwSuggestedAction = WizardManagerHelper.isDeferredSetupWizard(intent)
|
||||||
|
|| WizardManagerHelper.isPortalSetupWizard(intent)
|
||||||
|
|| intent.getBooleanExtra(EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW, false);
|
||||||
|
mSuwExtras = getSuwExtras(mIsSuw, intent);
|
||||||
|
mIsFromSettingsSummery = intent.getBooleanExtra(EXTRA_FROM_SETTINGS_SUMMARY, false);
|
||||||
|
mTheme = SetupWizardUtils.getTheme(context, intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuw() {
|
||||||
|
return mIsSuw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAfterSuwOrSuwSuggestedAction() {
|
||||||
|
return mIsAfterSuwOrSuwSuggestedAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFromSettingsSummery() {
|
||||||
|
return mIsFromSettingsSummery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTheme() {
|
||||||
|
return mTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Bundle getSuwExtras() {
|
||||||
|
return new Bundle(mSuwExtras);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of the object
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + ":{isSuw:" + mIsSuw
|
||||||
|
+ ", isAfterSuwOrSuwSuggestedAction:" + mIsAfterSuwOrSuwSuggestedAction
|
||||||
|
+ ", isFromSettingsSummery:" + mIsFromSettingsSummery
|
||||||
|
+ "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Bundle getSuwExtras(boolean isSuw, @NonNull Intent intent) {
|
||||||
|
final Intent toIntent = new Intent();
|
||||||
|
if (isSuw) {
|
||||||
|
SetupWizardUtils.copySetupExtras(intent, toIntent);
|
||||||
|
}
|
||||||
|
return toIntent.getExtras() != null ? toIntent.getExtras() : new Bundle();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import android.annotation.IntDef;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fingerprint onboarding introduction page data, it contains following information which needs
|
||||||
|
* to be passed from view model to view.
|
||||||
|
* 1. mEnrollableStatus: User is allowed to enroll a new fingerprint or not.
|
||||||
|
* 2. mHasScrollToBottom: User has scrolled to the bottom of this page or not.
|
||||||
|
*/
|
||||||
|
public final class FingerprintEnrollIntroStatus {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unconfirmed case, it means that this value is invalid, and view shall bypass this value.
|
||||||
|
*/
|
||||||
|
public static final int FINGERPRINT_ENROLLABLE_UNKNOWN = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User is allowed to enrolled a new fingerprint.
|
||||||
|
*/
|
||||||
|
public static final int FINGERPRINT_ENROLLABLE_OK = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User is not allowed to enrolled a new fingerprint because the number of enrolled fingerprint
|
||||||
|
* has reached maximum.
|
||||||
|
*/
|
||||||
|
public static final int FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX = 1;
|
||||||
|
|
||||||
|
@IntDef(prefix = {"FINGERPRINT_ENROLLABLE_"}, value = {
|
||||||
|
FINGERPRINT_ENROLLABLE_UNKNOWN,
|
||||||
|
FINGERPRINT_ENROLLABLE_OK,
|
||||||
|
FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
||||||
|
})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface FingerprintEnrollableStatus {
|
||||||
|
}
|
||||||
|
|
||||||
|
private final boolean mHasScrollToBottom;
|
||||||
|
|
||||||
|
@FingerprintEnrollableStatus
|
||||||
|
private final int mEnrollableStatus;
|
||||||
|
|
||||||
|
public FingerprintEnrollIntroStatus(boolean hasScrollToBottom, int enrollableStatus) {
|
||||||
|
mEnrollableStatus = enrollableStatus;
|
||||||
|
mHasScrollToBottom = hasScrollToBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get enrollable status. It means that user is allowed to enroll a new fingerprint or not.
|
||||||
|
*/
|
||||||
|
@FingerprintEnrollableStatus
|
||||||
|
public int getEnrollableStatus() {
|
||||||
|
return mEnrollableStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get info for this onboarding introduction page has scrolled to bottom or not
|
||||||
|
*/
|
||||||
|
public boolean hasScrollToBottom() {
|
||||||
|
return mHasScrollToBottom;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,288 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED;
|
||||||
|
|
||||||
|
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_UNKNOWN;
|
||||||
|
|
||||||
|
import static com.google.android.setupdesign.util.DynamicColorPalette.ColorType.ACCENT;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.admin.DevicePolicyResourcesManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.PorterDuffColorFilter;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus;
|
||||||
|
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel;
|
||||||
|
|
||||||
|
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||||
|
import com.google.android.setupcompat.template.FooterButton;
|
||||||
|
import com.google.android.setupdesign.GlifLayout;
|
||||||
|
import com.google.android.setupdesign.template.RequireScrollMixin;
|
||||||
|
import com.google.android.setupdesign.util.DynamicColorPalette;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fingerprint intro onboarding page fragment implementation
|
||||||
|
*/
|
||||||
|
public class FingerprintEnrollIntroFragment extends Fragment {
|
||||||
|
|
||||||
|
private static final String TAG = "FingerprintEnrollIntroFragment";
|
||||||
|
|
||||||
|
@NonNull private final ViewModelProvider mViewModelProvider;
|
||||||
|
@Nullable private final DevicePolicyResourcesManager mDevicePolicyMgrRes;
|
||||||
|
|
||||||
|
private FingerprintEnrollIntroViewModel mViewModel = null;
|
||||||
|
|
||||||
|
private View mView = null;
|
||||||
|
private FooterButton mPrimaryFooterButton = null;
|
||||||
|
private FooterButton mSecondaryFooterButton = null;
|
||||||
|
private ImageView mIconShield = null;
|
||||||
|
private TextView mFooterMessage6 = null;
|
||||||
|
@Nullable private PorterDuffColorFilter mIconColorFilter;
|
||||||
|
|
||||||
|
public FingerprintEnrollIntroFragment(
|
||||||
|
@NonNull ViewModelProvider viewModelProvider,
|
||||||
|
@Nullable DevicePolicyResourcesManager devicePolicyMgrRes) {
|
||||||
|
super();
|
||||||
|
mViewModelProvider = viewModelProvider;
|
||||||
|
mDevicePolicyMgrRes = devicePolicyMgrRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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_introduction, container);
|
||||||
|
|
||||||
|
final ImageView iconFingerprint = mView.findViewById(R.id.icon_fingerprint);
|
||||||
|
final ImageView iconDeviceLocked = mView.findViewById(R.id.icon_device_locked);
|
||||||
|
final ImageView iconTrashCan = mView.findViewById(R.id.icon_trash_can);
|
||||||
|
final ImageView iconInfo = mView.findViewById(R.id.icon_info);
|
||||||
|
mIconShield = mView.findViewById(R.id.icon_shield);
|
||||||
|
final ImageView iconLink = mView.findViewById(R.id.icon_link);
|
||||||
|
iconFingerprint.getDrawable().setColorFilter(getIconColorFilter(context));
|
||||||
|
iconDeviceLocked.getDrawable().setColorFilter(getIconColorFilter(context));
|
||||||
|
iconTrashCan.getDrawable().setColorFilter(getIconColorFilter(context));
|
||||||
|
iconInfo.getDrawable().setColorFilter(getIconColorFilter(context));
|
||||||
|
mIconShield.getDrawable().setColorFilter(getIconColorFilter(context));
|
||||||
|
iconLink.getDrawable().setColorFilter(getIconColorFilter(context));
|
||||||
|
|
||||||
|
final TextView footerMessage2 = mView.findViewById(R.id.footer_message_2);
|
||||||
|
final TextView footerMessage3 = mView.findViewById(R.id.footer_message_3);
|
||||||
|
final TextView footerMessage4 = mView.findViewById(R.id.footer_message_4);
|
||||||
|
final TextView footerMessage5 = mView.findViewById(R.id.footer_message_5);
|
||||||
|
mFooterMessage6 = mView.findViewById(R.id.footer_message_6);
|
||||||
|
footerMessage2.setText(
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_2);
|
||||||
|
footerMessage3.setText(
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_3);
|
||||||
|
footerMessage4.setText(
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_4);
|
||||||
|
footerMessage5.setText(
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_5);
|
||||||
|
mFooterMessage6.setText(
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_6);
|
||||||
|
|
||||||
|
final TextView footerTitle1 = mView.findViewById(R.id.footer_title_1);
|
||||||
|
final TextView footerTitle2 = mView.findViewById(R.id.footer_title_2);
|
||||||
|
footerTitle1.setText(
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_footer_title_1);
|
||||||
|
footerTitle2.setText(
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_footer_title_2);
|
||||||
|
|
||||||
|
return mView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
final Context context = view.getContext();
|
||||||
|
|
||||||
|
final TextView footerLink = mView.findViewById(R.id.footer_learn_more);
|
||||||
|
footerLink.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
final String footerLinkStr = getContext().getString(
|
||||||
|
R.string.security_settings_fingerprint_v2_enroll_introduction_message_learn_more,
|
||||||
|
Html.FROM_HTML_MODE_LEGACY);
|
||||||
|
footerLink.setText(Html.fromHtml(footerLinkStr));
|
||||||
|
|
||||||
|
// footer buttons
|
||||||
|
mPrimaryFooterButton = new FooterButton.Builder(context)
|
||||||
|
.setText(R.string.security_settings_fingerprint_enroll_introduction_agree)
|
||||||
|
.setListener(mViewModel::onNextButtonClick)
|
||||||
|
.setButtonType(FooterButton.ButtonType.OPT_IN)
|
||||||
|
.setTheme(R.style.SudGlifButton_Primary)
|
||||||
|
.build();
|
||||||
|
mSecondaryFooterButton = new FooterButton.Builder(context)
|
||||||
|
.setListener(mViewModel::onSkipOrCancelButtonClick)
|
||||||
|
.setButtonType(FooterButton.ButtonType.NEXT)
|
||||||
|
.setTheme(R.style.SudGlifButton_Primary)
|
||||||
|
.build();
|
||||||
|
getFooterBarMixin().setPrimaryButton(mPrimaryFooterButton);
|
||||||
|
getFooterBarMixin().setSecondaryButton(mSecondaryFooterButton, true /* usePrimaryStyle */);
|
||||||
|
|
||||||
|
if (mViewModel.canAssumeUdfps()) {
|
||||||
|
mFooterMessage6.setVisibility(View.VISIBLE);
|
||||||
|
mIconShield.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
mFooterMessage6.setVisibility(View.GONE);
|
||||||
|
mIconShield.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
mSecondaryFooterButton.setText(getContext(),
|
||||||
|
mViewModel.getEnrollmentRequest().isAfterSuwOrSuwSuggestedAction()
|
||||||
|
? R.string.security_settings_fingerprint_enroll_introduction_cancel
|
||||||
|
: R.string.security_settings_fingerprint_enroll_introduction_no_thanks);
|
||||||
|
|
||||||
|
if (mViewModel.isBiometricUnlockDisabledByAdmin()
|
||||||
|
&& !mViewModel.isParentalConsentRequired()) {
|
||||||
|
setHeaderText(
|
||||||
|
getActivity(),
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_title_unlock_disabled
|
||||||
|
);
|
||||||
|
getLayout().setDescriptionText(getDescriptionDisabledByAdmin(context));
|
||||||
|
} else {
|
||||||
|
setHeaderText(getActivity(),
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_title);
|
||||||
|
}
|
||||||
|
|
||||||
|
mViewModel.getPageStatusLiveData().observe(this, this::updateFooterButtons);
|
||||||
|
|
||||||
|
final RequireScrollMixin requireScrollMixin = getLayout()
|
||||||
|
.getMixin(RequireScrollMixin.class);
|
||||||
|
requireScrollMixin.requireScrollWithButton(getActivity(), mPrimaryFooterButton,
|
||||||
|
getMoreButtonTextRes(), mViewModel::onNextButtonClick);
|
||||||
|
requireScrollMixin.setOnRequireScrollStateChangedListener(scrollNeeded -> {
|
||||||
|
if (!scrollNeeded) {
|
||||||
|
mViewModel.setHasScrolledToBottom();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(@NonNull Context context) {
|
||||||
|
mViewModel = mViewModelProvider.get(FingerprintEnrollIntroViewModel.class);
|
||||||
|
getLifecycle().addObserver(mViewModel);
|
||||||
|
super.onAttach(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetach() {
|
||||||
|
getLifecycle().removeObserver(mViewModel);
|
||||||
|
super.onDetach();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private PorterDuffColorFilter getIconColorFilter(@NonNull Context context) {
|
||||||
|
if (mIconColorFilter == null) {
|
||||||
|
mIconColorFilter = new PorterDuffColorFilter(
|
||||||
|
DynamicColorPalette.getColor(context, ACCENT),
|
||||||
|
PorterDuff.Mode.SRC_IN);
|
||||||
|
}
|
||||||
|
return mIconColorFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GlifLayout getLayout() {
|
||||||
|
return mView.findViewById(R.id.setup_wizard_layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private FooterBarMixin getFooterBarMixin() {
|
||||||
|
final GlifLayout layout = getLayout();
|
||||||
|
return layout.getMixin(FooterBarMixin.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private String getDescriptionDisabledByAdmin(@NonNull Context context) {
|
||||||
|
final int defaultStrId =
|
||||||
|
R.string.security_settings_fingerprint_enroll_introduction_message_unlock_disabled;
|
||||||
|
if (mDevicePolicyMgrRes == null) {
|
||||||
|
Log.w(TAG, "getDescriptionDisabledByAdmin, null device policy manager res");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return mDevicePolicyMgrRes.getString(FINGERPRINT_UNLOCK_DISABLED,
|
||||||
|
() -> context.getString(defaultStrId));
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
@StringRes final int scrollToBottomPrimaryResId =
|
||||||
|
status.getEnrollableStatus() == FINGERPRINT_ENROLLABLE_OK
|
||||||
|
? R.string.security_settings_fingerprint_enroll_introduction_agree
|
||||||
|
: R.string.done;
|
||||||
|
|
||||||
|
mPrimaryFooterButton.setText(getContext(),
|
||||||
|
status.hasScrollToBottom() ? scrollToBottomPrimaryResId : getMoreButtonTextRes());
|
||||||
|
mSecondaryFooterButton.setVisibility(
|
||||||
|
status.hasScrollToBottom() ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
|
||||||
|
final TextView errorTextView = mView.findViewById(R.id.error_text);
|
||||||
|
switch (status.getEnrollableStatus()) {
|
||||||
|
case FINGERPRINT_ENROLLABLE_OK:
|
||||||
|
errorTextView.setText(null);
|
||||||
|
errorTextView.setVisibility(View.GONE);
|
||||||
|
break;
|
||||||
|
case FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX:
|
||||||
|
errorTextView.setText(R.string.fingerprint_intro_error_max);
|
||||||
|
errorTextView.setVisibility(View.VISIBLE);
|
||||||
|
break;
|
||||||
|
case FINGERPRINT_ENROLLABLE_UNKNOWN:
|
||||||
|
// default case, do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
private int getMoreButtonTextRes() {
|
||||||
|
return R.string.security_settings_face_enroll_introduction_more;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,276 @@
|
|||||||
|
/*
|
||||||
|
* 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 androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY;
|
||||||
|
|
||||||
|
import static com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR;
|
||||||
|
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE;
|
||||||
|
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK;
|
||||||
|
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK;
|
||||||
|
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_SKIP_OR_CANCEL;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResult;
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.lifecycle.viewmodel.CreationExtras;
|
||||||
|
import androidx.lifecycle.viewmodel.MutableCreationExtras;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.Utils;
|
||||||
|
import com.android.settings.biometrics.BiometricEnrollBase;
|
||||||
|
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor;
|
||||||
|
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollEnrolling;
|
||||||
|
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||||
|
import com.android.settings.biometrics2.factory.BiometricsFragmentFactory;
|
||||||
|
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory;
|
||||||
|
import com.android.settings.biometrics2.ui.model.CredentialModel;
|
||||||
|
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
|
||||||
|
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel;
|
||||||
|
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.FingerprintChallengeGenerator;
|
||||||
|
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel;
|
||||||
|
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel;
|
||||||
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
|
|
||||||
|
import com.google.android.setupdesign.util.ThemeHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fingerprint enrollment activity implementation
|
||||||
|
*/
|
||||||
|
public class FingerprintEnrollmentActivity extends FragmentActivity {
|
||||||
|
|
||||||
|
private static final String TAG = "FingerprintEnrollmentActivity";
|
||||||
|
|
||||||
|
protected static final int LAUNCH_CONFIRM_LOCK_ACTIVITY = 1;
|
||||||
|
|
||||||
|
private FingerprintEnrollmentViewModel mViewModel;
|
||||||
|
private AutoCredentialViewModel mAutoCredentialViewModel;
|
||||||
|
private ActivityResultLauncher<Intent> mNextActivityLauncher;
|
||||||
|
private ActivityResultLauncher<Intent> mChooseLockLauncher;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
mNextActivityLauncher = registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
(it) -> mViewModel.onContinueEnrollActivityResult(
|
||||||
|
it,
|
||||||
|
mAutoCredentialViewModel.getUserId())
|
||||||
|
);
|
||||||
|
mChooseLockLauncher = registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
(it) -> onChooseOrConfirmLockResult(true, it)
|
||||||
|
);
|
||||||
|
|
||||||
|
ViewModelProvider viewModelProvider = new ViewModelProvider(this);
|
||||||
|
|
||||||
|
mViewModel = viewModelProvider.get(FingerprintEnrollmentViewModel.class);
|
||||||
|
mViewModel.setRequest(new EnrollmentRequest(getIntent(), getApplicationContext()));
|
||||||
|
mViewModel.setSavedInstanceState(savedInstanceState);
|
||||||
|
getLifecycle().addObserver(mViewModel);
|
||||||
|
|
||||||
|
mAutoCredentialViewModel = viewModelProvider.get(AutoCredentialViewModel.class);
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(new CredentialModel(getIntent(),
|
||||||
|
SystemClock.elapsedRealtimeClock()));
|
||||||
|
getLifecycle().addObserver(mAutoCredentialViewModel);
|
||||||
|
|
||||||
|
mViewModel.getSetResultLiveData().observe(this, this::onSetActivityResult);
|
||||||
|
mAutoCredentialViewModel.getActionLiveData().observe(this, this::onCredentialAction);
|
||||||
|
|
||||||
|
// Theme
|
||||||
|
setTheme(mViewModel.getRequest().getTheme());
|
||||||
|
ThemeHelper.trySetDynamicColor(this);
|
||||||
|
getWindow().setStatusBarColor(android.graphics.Color.TRANSPARENT);
|
||||||
|
|
||||||
|
// fragment
|
||||||
|
setContentView(R.layout.biometric_enrollment_container);
|
||||||
|
final FragmentManager fragmentManager = getSupportFragmentManager();
|
||||||
|
fragmentManager.setFragmentFactory(
|
||||||
|
new BiometricsFragmentFactory(getApplication(), viewModelProvider));
|
||||||
|
|
||||||
|
final FingerprintEnrollIntroViewModel fingerprintEnrollIntroViewModel =
|
||||||
|
viewModelProvider.get(FingerprintEnrollIntroViewModel.class);
|
||||||
|
fingerprintEnrollIntroViewModel.setEnrollmentRequest(mViewModel.getRequest());
|
||||||
|
fingerprintEnrollIntroViewModel.setUserId(mAutoCredentialViewModel.getUserId());
|
||||||
|
fingerprintEnrollIntroViewModel.getActionLiveData().observe(
|
||||||
|
this, this::observeIntroAction);
|
||||||
|
final String tag = "FingerprintEnrollIntroFragment";
|
||||||
|
fragmentManager.beginTransaction()
|
||||||
|
.setReorderingAllowed(true)
|
||||||
|
.add(R.id.fragment_container_view, FingerprintEnrollIntroFragment.class, null, tag)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSetActivityResult(@NonNull ActivityResult result) {
|
||||||
|
setResult(mViewModel.getRequest().isAfterSuwOrSuwSuggestedAction()
|
||||||
|
? RESULT_CANCELED
|
||||||
|
: result.getResultCode(),
|
||||||
|
result.getData());
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCredentialAction(@NonNull Integer action) {
|
||||||
|
switch (action) {
|
||||||
|
case CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK: {
|
||||||
|
final Intent intent = mAutoCredentialViewModel.getChooseLockIntent(this,
|
||||||
|
mViewModel.getRequest().isSuw(), mViewModel.getRequest().getSuwExtras());
|
||||||
|
if (!mViewModel.isWaitingActivityResult().compareAndSet(false, true)) {
|
||||||
|
Log.w(TAG, "chooseLock, fail to set isWaiting flag to true");
|
||||||
|
}
|
||||||
|
mChooseLockLauncher.launch(intent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK: {
|
||||||
|
final boolean launched = mAutoCredentialViewModel.getConfirmLockLauncher(
|
||||||
|
this,
|
||||||
|
LAUNCH_CONFIRM_LOCK_ACTIVITY,
|
||||||
|
getString(R.string.security_settings_fingerprint_preference_title)
|
||||||
|
).launch();
|
||||||
|
if (!launched) {
|
||||||
|
// This shouldn't happen, as we should only end up at this step if a lock thingy
|
||||||
|
// is already set.
|
||||||
|
Log.e(TAG, "confirmLock, launched is true");
|
||||||
|
finish();
|
||||||
|
} else if (!mViewModel.isWaitingActivityResult().compareAndSet(false, true)) {
|
||||||
|
Log.w(TAG, "confirmLock, fail to set isWaiting flag to true");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE: {
|
||||||
|
Log.w(TAG, "observeCredentialLiveData, finish with action:" + action);
|
||||||
|
if (mViewModel.getRequest().isAfterSuwOrSuwSuggestedAction()) {
|
||||||
|
setResult(Activity.RESULT_CANCELED);
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onChooseOrConfirmLockResult(boolean isChooseLock,
|
||||||
|
@NonNull ActivityResult activityResult) {
|
||||||
|
if (!mViewModel.isWaitingActivityResult().compareAndSet(true, false)) {
|
||||||
|
Log.w(TAG, "isChooseLock:" + isChooseLock + ", fail to unset waiting flag");
|
||||||
|
}
|
||||||
|
if (mAutoCredentialViewModel.checkNewCredentialFromActivityResult(
|
||||||
|
isChooseLock, activityResult)) {
|
||||||
|
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void observeIntroAction(@NonNull Integer action) {
|
||||||
|
switch (action) {
|
||||||
|
case FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH: {
|
||||||
|
onSetActivityResult(
|
||||||
|
new ActivityResult(BiometricEnrollBase.RESULT_FINISHED, null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL: {
|
||||||
|
onSetActivityResult(
|
||||||
|
new ActivityResult(BiometricEnrollBase.RESULT_SKIP, null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL: {
|
||||||
|
final boolean isSuw = mViewModel.getRequest().isSuw();
|
||||||
|
if (!mViewModel.isWaitingActivityResult().compareAndSet(false, true)) {
|
||||||
|
Log.w(TAG, "startNext, isSuw:" + isSuw + ", fail to set isWaiting flag");
|
||||||
|
}
|
||||||
|
final Intent intent = new Intent(this, isSuw
|
||||||
|
? SetupFingerprintEnrollEnrolling.class
|
||||||
|
: FingerprintEnrollFindSensor.class);
|
||||||
|
intent.putExtras(mAutoCredentialViewModel.getCredentialBundle());
|
||||||
|
intent.putExtras(mViewModel.getNextActivityBaseIntentExtras());
|
||||||
|
mNextActivityLauncher.launch(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
mViewModel.checkFinishActivityDuringOnPause(isFinishing(), isChangingConfigurations());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||||
|
if (requestCode == LAUNCH_CONFIRM_LOCK_ACTIVITY) {
|
||||||
|
onChooseOrConfirmLockResult(false, new ActivityResult(resultCode, data));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public CreationExtras getDefaultViewModelCreationExtras() {
|
||||||
|
final Application application =
|
||||||
|
super.getDefaultViewModelCreationExtras().get(APPLICATION_KEY);
|
||||||
|
final MutableCreationExtras ret = new MutableCreationExtras();
|
||||||
|
ret.set(APPLICATION_KEY, application);
|
||||||
|
final FingerprintRepository repository = FeatureFactory.getFactory(application)
|
||||||
|
.getBiometricsRepositoryProvider().getFingerprintRepository(application);
|
||||||
|
ret.set(CHALLENGE_GENERATOR, new FingerprintChallengeGenerator(repository));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
|
||||||
|
return new BiometricsViewModelFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
getWindow().setStatusBarColor(getBackgroundColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
private int getBackgroundColor() {
|
||||||
|
final ColorStateList stateList = Utils.getColorAttr(this, android.R.attr.windowBackground);
|
||||||
|
return stateList != null ? stateList.getDefaultColor() : Color.TRANSPARENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
getLifecycle().removeObserver(mViewModel);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
mViewModel.onSaveInstanceState(outState);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,354 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
|
||||||
|
|
||||||
|
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE;
|
||||||
|
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_SENSOR_ID;
|
||||||
|
import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_GK_PW_HANDLE;
|
||||||
|
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN;
|
||||||
|
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;
|
||||||
|
|
||||||
|
import android.annotation.IntDef;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResult;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.android.internal.widget.LockPatternUtils;
|
||||||
|
import com.android.internal.widget.VerifyCredentialResponse;
|
||||||
|
import com.android.settings.biometrics.BiometricUtils;
|
||||||
|
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||||
|
import com.android.settings.biometrics2.ui.model.CredentialModel;
|
||||||
|
import com.android.settings.password.ChooseLockGeneric;
|
||||||
|
import com.android.settings.password.ChooseLockPattern;
|
||||||
|
import com.android.settings.password.ChooseLockSettingsHelper;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AutoCredentialViewModel which uses CredentialModel to determine next actions for activity, like
|
||||||
|
* start ChooseLockActivity, start ConfirmLockActivity, GenerateCredential, or do nothing.
|
||||||
|
*/
|
||||||
|
public class AutoCredentialViewModel extends AndroidViewModel implements DefaultLifecycleObserver {
|
||||||
|
|
||||||
|
private static final String TAG = "AutoCredentialViewModel";
|
||||||
|
private static final boolean DEBUG = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Need activity to run choose lock
|
||||||
|
*/
|
||||||
|
public static final int CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Need activity to run confirm lock
|
||||||
|
*/
|
||||||
|
public static final int CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fail to use challenge from hardware generateChallenge(), shall finish activity with proper
|
||||||
|
* error code
|
||||||
|
*/
|
||||||
|
public static final int CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE = 3;
|
||||||
|
|
||||||
|
@IntDef(prefix = { "CREDENTIAL_" }, value = {
|
||||||
|
CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK,
|
||||||
|
CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK,
|
||||||
|
CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE
|
||||||
|
})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface CredentialAction {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic callback for FingerprintManager#generateChallenge or FaceManager#generateChallenge
|
||||||
|
*/
|
||||||
|
public interface GenerateChallengeCallback {
|
||||||
|
/**
|
||||||
|
* Generic generateChallenge method for FingerprintManager or FaceManager
|
||||||
|
*/
|
||||||
|
void onChallengeGenerated(int sensorId, int userId, long challenge);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic interface class for calling different generateChallenge from FingerprintManager or
|
||||||
|
* FaceManager
|
||||||
|
*/
|
||||||
|
public interface ChallengeGenerator {
|
||||||
|
/**
|
||||||
|
* Get callback that will be called later after challenge generated
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
GenerateChallengeCallback getCallback();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set callback that will be called later after challenge generated
|
||||||
|
*/
|
||||||
|
void setCallback(@Nullable GenerateChallengeCallback callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method for generating challenge from FingerprintManager or FaceManager
|
||||||
|
*/
|
||||||
|
void generateChallenge(int userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to generate challenge through FingerprintRepository
|
||||||
|
*/
|
||||||
|
public static class FingerprintChallengeGenerator implements ChallengeGenerator {
|
||||||
|
|
||||||
|
private static final String TAG = "FingerprintChallengeGenerator";
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final FingerprintRepository mFingerprintRepository;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private GenerateChallengeCallback mCallback = null;
|
||||||
|
|
||||||
|
public FingerprintChallengeGenerator(@NonNull FingerprintRepository fingerprintRepository) {
|
||||||
|
mFingerprintRepository = fingerprintRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public GenerateChallengeCallback getCallback() {
|
||||||
|
return mCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCallback(@Nullable GenerateChallengeCallback callback) {
|
||||||
|
mCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void generateChallenge(int userId) {
|
||||||
|
final GenerateChallengeCallback callback = mCallback;
|
||||||
|
if (callback == null) {
|
||||||
|
Log.e(TAG, "generateChallenge, null callback");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mFingerprintRepository.generateChallenge(userId, callback::onChallengeGenerated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull private final LockPatternUtils mLockPatternUtils;
|
||||||
|
@NonNull private final ChallengeGenerator mChallengeGenerator;
|
||||||
|
private CredentialModel mCredentialModel = null;
|
||||||
|
@NonNull private final MutableLiveData<Integer> mActionLiveData =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
|
||||||
|
public AutoCredentialViewModel(
|
||||||
|
@NonNull Application application,
|
||||||
|
@NonNull LockPatternUtils lockPatternUtils,
|
||||||
|
@NonNull ChallengeGenerator challengeGenerator) {
|
||||||
|
super(application);
|
||||||
|
mLockPatternUtils = lockPatternUtils;
|
||||||
|
mChallengeGenerator = challengeGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCredentialModel(@NonNull CredentialModel credentialModel) {
|
||||||
|
mCredentialModel = credentialModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe ActionLiveData for actions about choosing lock, confirming lock, or finishing
|
||||||
|
* activity
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public LiveData<Integer> getActionLiveData() {
|
||||||
|
return mActionLiveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@NonNull LifecycleOwner owner) {
|
||||||
|
checkCredential();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check credential status for biometric enrollment.
|
||||||
|
*/
|
||||||
|
private void checkCredential() {
|
||||||
|
if (isValidCredential()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final long gkPwHandle = mCredentialModel.getGkPwHandle();
|
||||||
|
if (isUnspecifiedPassword()) {
|
||||||
|
mActionLiveData.postValue(CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK);
|
||||||
|
} else if (CredentialModel.isValidGkPwHandle(gkPwHandle)) {
|
||||||
|
generateChallenge(gkPwHandle);
|
||||||
|
} else {
|
||||||
|
mActionLiveData.postValue(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateChallenge(long gkPwHandle) {
|
||||||
|
mChallengeGenerator.setCallback((sensorId, userId, challenge) -> {
|
||||||
|
mCredentialModel.setSensorId(sensorId);
|
||||||
|
mCredentialModel.setChallenge(challenge);
|
||||||
|
try {
|
||||||
|
final byte[] newToken = requestGatekeeperHat(gkPwHandle, challenge, userId);
|
||||||
|
mCredentialModel.setToken(newToken);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
Log.e(TAG, "generateChallenge, IllegalStateException", e);
|
||||||
|
mActionLiveData.postValue(CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mLockPatternUtils.removeGatekeeperPasswordHandle(gkPwHandle);
|
||||||
|
mCredentialModel.clearGkPwHandle();
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "generateChallenge " + mCredentialModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check credential again
|
||||||
|
if (!isValidCredential()) {
|
||||||
|
Log.w(TAG, "generateChallenge, invalid Credential");
|
||||||
|
mActionLiveData.postValue(CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mChallengeGenerator.generateChallenge(getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidCredential() {
|
||||||
|
return !isUnspecifiedPassword()
|
||||||
|
&& CredentialModel.isValidToken(mCredentialModel.getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isUnspecifiedPassword() {
|
||||||
|
return mLockPatternUtils.getActivePasswordQuality(getUserId())
|
||||||
|
== PASSWORD_QUALITY_UNSPECIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle activity result from ChooseLockGeneric, ConfirmLockPassword, or ConfirmLockPattern
|
||||||
|
* @param isChooseLock true if result is coming from ChooseLockGeneric. False if result is
|
||||||
|
* coming from ConfirmLockPassword or ConfirmLockPattern
|
||||||
|
* @param result activity result
|
||||||
|
* @return if it is a valid result
|
||||||
|
*/
|
||||||
|
public boolean checkNewCredentialFromActivityResult(boolean isChooseLock,
|
||||||
|
@NonNull ActivityResult result) {
|
||||||
|
if ((isChooseLock && result.getResultCode() == ChooseLockPattern.RESULT_FINISHED)
|
||||||
|
|| (!isChooseLock && result.getResultCode() == Activity.RESULT_OK)) {
|
||||||
|
final Intent data = result.getData();
|
||||||
|
if (data != null) {
|
||||||
|
final long gkPwHandle = result.getData().getLongExtra(
|
||||||
|
EXTRA_KEY_GK_PW_HANDLE, INVALID_GK_PW_HANDLE);
|
||||||
|
generateChallenge(gkPwHandle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get userId for this credential
|
||||||
|
*/
|
||||||
|
public int getUserId() {
|
||||||
|
return mCredentialModel.getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private byte[] requestGatekeeperHat(long gkPwHandle, long challenge, int userId)
|
||||||
|
throws IllegalStateException {
|
||||||
|
final VerifyCredentialResponse response = mLockPatternUtils
|
||||||
|
.verifyGatekeeperPasswordHandle(gkPwHandle, challenge, userId);
|
||||||
|
if (!response.isMatched()) {
|
||||||
|
throw new IllegalStateException("Unable to request Gatekeeper HAT");
|
||||||
|
}
|
||||||
|
return response.getGatekeeperHAT();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Credential bundle which will be used to launch next activity.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Bundle getCredentialBundle() {
|
||||||
|
final Bundle retBundle = new Bundle();
|
||||||
|
final long gkPwHandle = mCredentialModel.getGkPwHandle();
|
||||||
|
if (CredentialModel.isValidGkPwHandle(gkPwHandle)) {
|
||||||
|
retBundle.putLong(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
|
||||||
|
}
|
||||||
|
final byte[] token = mCredentialModel.getToken();
|
||||||
|
if (CredentialModel.isValidToken(token)) {
|
||||||
|
retBundle.putByteArray(EXTRA_KEY_CHALLENGE_TOKEN, token);
|
||||||
|
}
|
||||||
|
final int userId = getUserId();
|
||||||
|
if (CredentialModel.isValidUserId(userId)) {
|
||||||
|
retBundle.putInt(Intent.EXTRA_USER_ID, userId);
|
||||||
|
}
|
||||||
|
retBundle.putLong(EXTRA_KEY_CHALLENGE, mCredentialModel.getChallenge());
|
||||||
|
retBundle.putInt(EXTRA_KEY_SENSOR_ID, mCredentialModel.getSensorId());
|
||||||
|
return retBundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Intent for choosing lock
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Intent getChooseLockIntent(@NonNull Context context, boolean isSuw,
|
||||||
|
@NonNull Bundle suwExtras) {
|
||||||
|
final Intent intent = BiometricUtils.getChooseLockIntent(context, isSuw,
|
||||||
|
suwExtras);
|
||||||
|
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS,
|
||||||
|
true);
|
||||||
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true);
|
||||||
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, true);
|
||||||
|
|
||||||
|
final int userId = getUserId();
|
||||||
|
if (CredentialModel.isValidUserId(userId)) {
|
||||||
|
intent.putExtra(Intent.EXTRA_USER_ID, userId);
|
||||||
|
}
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ConfirmLockLauncher
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public ChooseLockSettingsHelper getConfirmLockLauncher(@NonNull Activity activity,
|
||||||
|
int requestCode, @NonNull String title) {
|
||||||
|
final ChooseLockSettingsHelper.Builder builder =
|
||||||
|
new ChooseLockSettingsHelper.Builder(activity);
|
||||||
|
builder.setRequestCode(requestCode)
|
||||||
|
.setTitle(title)
|
||||||
|
.setRequestGatekeeperPasswordHandle(true)
|
||||||
|
.setForegroundOnly(true)
|
||||||
|
.setReturnCredentials(true);
|
||||||
|
|
||||||
|
final int userId = mCredentialModel.getUserId();
|
||||||
|
if (CredentialModel.isValidUserId(userId)) {
|
||||||
|
builder.setUserId(userId);
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
* 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.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_UNKNOWN;
|
||||||
|
|
||||||
|
import android.annotation.IntDef;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MediatorLiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||||
|
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
|
||||||
|
import com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fingerprint intro onboarding page view model implementation
|
||||||
|
*/
|
||||||
|
public class FingerprintEnrollIntroViewModel extends AndroidViewModel
|
||||||
|
implements DefaultLifecycleObserver {
|
||||||
|
|
||||||
|
private static final String TAG = "FingerprintEnrollIntroViewModel";
|
||||||
|
private static final boolean HAS_SCROLLED_TO_BOTTOM_DEFAULT = false;
|
||||||
|
private static final int ENROLLABLE_STATUS_DEFAULT = FINGERPRINT_ENROLLABLE_UNKNOWN;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User clicks 'Done' button on this page
|
||||||
|
*/
|
||||||
|
public static final int FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User clicks 'Agree' button on this page
|
||||||
|
*/
|
||||||
|
public static final int FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User clicks 'Skip' button on this page
|
||||||
|
*/
|
||||||
|
public static final int FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL = 2;
|
||||||
|
|
||||||
|
@IntDef(prefix = { "FINGERPRINT_ENROLL_INTRO_ACTION_" }, value = {
|
||||||
|
FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH,
|
||||||
|
FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL,
|
||||||
|
FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL
|
||||||
|
})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface FingerprintEnrollIntroAction {}
|
||||||
|
|
||||||
|
@NonNull private final FingerprintRepository mFingerprintRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<Boolean> mHasScrolledToBottomLiveData =
|
||||||
|
new MutableLiveData<>(HAS_SCROLLED_TO_BOTTOM_DEFAULT);
|
||||||
|
private final MutableLiveData<Integer> mEnrollableStatusLiveData =
|
||||||
|
new MutableLiveData<>(ENROLLABLE_STATUS_DEFAULT);
|
||||||
|
private final MediatorLiveData<FingerprintEnrollIntroStatus> mPageStatusLiveData =
|
||||||
|
new MediatorLiveData<>();
|
||||||
|
private final MutableLiveData<Integer> mActionLiveData = new MutableLiveData<>();
|
||||||
|
private int mUserId = UserHandle.myUserId();
|
||||||
|
private EnrollmentRequest mEnrollmentRequest = null;
|
||||||
|
|
||||||
|
public FingerprintEnrollIntroViewModel(@NonNull Application application,
|
||||||
|
@NonNull FingerprintRepository fingerprintRepository) {
|
||||||
|
super(application);
|
||||||
|
mFingerprintRepository = fingerprintRepository;
|
||||||
|
|
||||||
|
mPageStatusLiveData.addSource(
|
||||||
|
mEnrollableStatusLiveData,
|
||||||
|
enrollable -> {
|
||||||
|
final Boolean toBottomValue = mHasScrolledToBottomLiveData.getValue();
|
||||||
|
final FingerprintEnrollIntroStatus status = new FingerprintEnrollIntroStatus(
|
||||||
|
toBottomValue != null ? toBottomValue : HAS_SCROLLED_TO_BOTTOM_DEFAULT,
|
||||||
|
enrollable);
|
||||||
|
mPageStatusLiveData.setValue(status);
|
||||||
|
});
|
||||||
|
mPageStatusLiveData.addSource(
|
||||||
|
mHasScrolledToBottomLiveData,
|
||||||
|
hasScrolledToBottom -> {
|
||||||
|
final Integer enrollableValue = mEnrollableStatusLiveData.getValue();
|
||||||
|
final FingerprintEnrollIntroStatus status = new FingerprintEnrollIntroStatus(
|
||||||
|
hasScrolledToBottom,
|
||||||
|
enrollableValue != null ? enrollableValue : ENROLLABLE_STATUS_DEFAULT);
|
||||||
|
mPageStatusLiveData.setValue(status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(int userId) {
|
||||||
|
mUserId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnrollmentRequest(@NonNull EnrollmentRequest enrollmentRequest) {
|
||||||
|
mEnrollmentRequest = enrollmentRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get enrollment request
|
||||||
|
*/
|
||||||
|
public EnrollmentRequest getEnrollmentRequest() {
|
||||||
|
return mEnrollmentRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEnrollableStatus() {
|
||||||
|
final int num = mFingerprintRepository.getNumOfEnrolledFingerprintsSize(mUserId);
|
||||||
|
final int max =
|
||||||
|
mEnrollmentRequest.isSuw() && !mEnrollmentRequest.isAfterSuwOrSuwSuggestedAction()
|
||||||
|
? mFingerprintRepository.getMaxFingerprintsInSuw(getApplication().getResources())
|
||||||
|
: mFingerprintRepository.getMaxFingerprints();
|
||||||
|
mEnrollableStatusLiveData.postValue(num >= max
|
||||||
|
? FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
||||||
|
: FINGERPRINT_ENROLLABLE_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get enrollable status and hasScrollToBottom live data
|
||||||
|
*/
|
||||||
|
public LiveData<FingerprintEnrollIntroStatus> getPageStatusLiveData() {
|
||||||
|
return mPageStatusLiveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user's action live data (like clicking Agree, Skip, or Done)
|
||||||
|
*/
|
||||||
|
public LiveData<Integer> getActionLiveData() {
|
||||||
|
return mActionLiveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first sensor type is UDFPS sensor or not
|
||||||
|
*/
|
||||||
|
public boolean canAssumeUdfps() {
|
||||||
|
return mFingerprintRepository.canAssumeUdfps();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update onboarding intro page has scrolled to bottom
|
||||||
|
*/
|
||||||
|
public void setHasScrolledToBottom() {
|
||||||
|
mHasScrolledToBottomLiveData.postValue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get parental consent required or not during enrollment process
|
||||||
|
*/
|
||||||
|
public boolean isParentalConsentRequired() {
|
||||||
|
return mFingerprintRepository.isParentalConsentRequired(getApplication());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fingerprint is disable by admin or not
|
||||||
|
*/
|
||||||
|
public boolean isBiometricUnlockDisabledByAdmin() {
|
||||||
|
return mFingerprintRepository.isDisabledByAdmin(getApplication(), mUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User clicks next button
|
||||||
|
*/
|
||||||
|
public void onNextButtonClick(View ignoredView) {
|
||||||
|
final Integer status = mEnrollableStatusLiveData.getValue();
|
||||||
|
switch (status != null ? status : ENROLLABLE_STATUS_DEFAULT) {
|
||||||
|
case FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX:
|
||||||
|
mActionLiveData.postValue(FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH);
|
||||||
|
break;
|
||||||
|
case FINGERPRINT_ENROLLABLE_OK:
|
||||||
|
mActionLiveData.postValue(FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.w(TAG, "fail to click next, enrolled:" + status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User clicks skip/cancel button
|
||||||
|
*/
|
||||||
|
public void onSkipOrCancelButtonClick(View ignoredView) {
|
||||||
|
mActionLiveData.postValue(FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart(@NonNull LifecycleOwner owner) {
|
||||||
|
updateEnrollableStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
* 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.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY;
|
||||||
|
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED;
|
||||||
|
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_SKIP;
|
||||||
|
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT;
|
||||||
|
import static com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction.EXTRA_FINGERPRINT_ENROLLED_COUNT;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.app.KeyguardManager;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResult;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.android.settings.biometrics.BiometricEnrollBase;
|
||||||
|
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||||
|
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
|
||||||
|
import com.android.settings.password.SetupSkipDialog;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fingerprint enrollment view model implementation
|
||||||
|
*/
|
||||||
|
public class FingerprintEnrollmentViewModel extends AndroidViewModel implements
|
||||||
|
DefaultLifecycleObserver {
|
||||||
|
|
||||||
|
private static final String TAG = "FingerprintEnrollmentViewModel";
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String SAVED_STATE_IS_WAITING_ACTIVITY_RESULT = "is_waiting_activity_result";
|
||||||
|
|
||||||
|
@NonNull private final FingerprintRepository mFingerprintRepository;
|
||||||
|
@Nullable private final KeyguardManager mKeyguardManager;
|
||||||
|
|
||||||
|
private final AtomicBoolean mIsWaitingActivityResult = new AtomicBoolean(false);
|
||||||
|
private final MutableLiveData<ActivityResult> mSetResultLiveData = new MutableLiveData<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Even this variable may be nullable, but activity will call setIntent() immediately during
|
||||||
|
* its onCreate(), we do not assign @Nullable for this variable here.
|
||||||
|
*/
|
||||||
|
private EnrollmentRequest mRequest = null;
|
||||||
|
|
||||||
|
public FingerprintEnrollmentViewModel(
|
||||||
|
@NonNull Application application,
|
||||||
|
@NonNull FingerprintRepository fingerprintRepository,
|
||||||
|
@Nullable KeyguardManager keyguardManager) {
|
||||||
|
super(application);
|
||||||
|
mFingerprintRepository = fingerprintRepository;
|
||||||
|
mKeyguardManager = keyguardManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set EnrollmentRequest
|
||||||
|
*/
|
||||||
|
public void setRequest(@NonNull EnrollmentRequest request) {
|
||||||
|
mRequest = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get EnrollmentRequest
|
||||||
|
*/
|
||||||
|
public EnrollmentRequest getRequest() {
|
||||||
|
return mRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy necessary extra data from activity intent
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Bundle getNextActivityBaseIntentExtras() {
|
||||||
|
final Bundle bundle = mRequest.getSuwExtras();
|
||||||
|
bundle.putBoolean(EXTRA_FROM_SETTINGS_SUMMARY, mRequest.isFromSettingsSummery());
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle activity result from FingerprintFindSensor
|
||||||
|
*/
|
||||||
|
public void onContinueEnrollActivityResult(@NonNull ActivityResult result, int userId) {
|
||||||
|
if (mIsWaitingActivityResult.compareAndSet(true, false)) {
|
||||||
|
Log.w(TAG, "fail to reset isWaiting flag for enrollment");
|
||||||
|
}
|
||||||
|
if (result.getResultCode() == RESULT_FINISHED
|
||||||
|
|| result.getResultCode() == RESULT_TIMEOUT) {
|
||||||
|
Intent data = result.getData();
|
||||||
|
if (mRequest.isSuw() && isKeyguardSecure()
|
||||||
|
&& result.getResultCode() == RESULT_FINISHED) {
|
||||||
|
if (data == null) {
|
||||||
|
data = new Intent();
|
||||||
|
}
|
||||||
|
data.putExtras(getSuwFingerprintCountExtra(userId));
|
||||||
|
}
|
||||||
|
mSetResultLiveData.postValue(new ActivityResult(result.getResultCode(), data));
|
||||||
|
} else if (result.getResultCode() == RESULT_SKIP
|
||||||
|
|| result.getResultCode() == SetupSkipDialog.RESULT_SKIP) {
|
||||||
|
mSetResultLiveData.postValue(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isKeyguardSecure() {
|
||||||
|
return mKeyguardManager != null && mKeyguardManager.isKeyguardSecure();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity calls this method during onPause() to finish itself when back to background.
|
||||||
|
*
|
||||||
|
* @param isActivityFinishing Activity has called finish() or not
|
||||||
|
* @param isChangingConfigurations Activity is finished because of configuration changed or not.
|
||||||
|
*/
|
||||||
|
public void checkFinishActivityDuringOnPause(boolean isActivityFinishing,
|
||||||
|
boolean isChangingConfigurations) {
|
||||||
|
if (isChangingConfigurations || isActivityFinishing || mRequest.isSuw()
|
||||||
|
|| isWaitingActivityResult().get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mSetResultLiveData.postValue(new ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Bundle getSuwFingerprintCountExtra(int userId) {
|
||||||
|
final Bundle bundle = new Bundle();
|
||||||
|
bundle.putInt(EXTRA_FINGERPRINT_ENROLLED_COUNT,
|
||||||
|
mFingerprintRepository.getNumOfEnrolledFingerprintsSize(userId));
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public LiveData<ActivityResult> getSetResultLiveData() {
|
||||||
|
return mSetResultLiveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public AtomicBoolean isWaitingActivityResult() {
|
||||||
|
return mIsWaitingActivityResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle savedInstanceState from activity onCreated()
|
||||||
|
*/
|
||||||
|
public void setSavedInstanceState(@Nullable Bundle savedInstanceState) {
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mIsWaitingActivityResult.set(
|
||||||
|
savedInstanceState.getBoolean(SAVED_STATE_IS_WAITING_ACTIVITY_RESULT, false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle onSaveInstanceState from activity
|
||||||
|
*/
|
||||||
|
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
outState.putBoolean(SAVED_STATE_IS_WAITING_ACTIVITY_RESULT, mIsWaitingActivityResult.get());
|
||||||
|
}
|
||||||
|
}
|
@@ -29,6 +29,7 @@ import com.android.settings.accounts.AccountFeatureProvider;
|
|||||||
import com.android.settings.applications.ApplicationFeatureProvider;
|
import com.android.settings.applications.ApplicationFeatureProvider;
|
||||||
import com.android.settings.aware.AwareFeatureProvider;
|
import com.android.settings.aware.AwareFeatureProvider;
|
||||||
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
||||||
|
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
|
||||||
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
||||||
import com.android.settings.dashboard.DashboardFeatureProvider;
|
import com.android.settings.dashboard.DashboardFeatureProvider;
|
||||||
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
|
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
|
||||||
@@ -161,6 +162,11 @@ public abstract class FeatureFactory {
|
|||||||
|
|
||||||
public abstract FaceFeatureProvider getFaceFeatureProvider();
|
public abstract FaceFeatureProvider getFaceFeatureProvider();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets implementation for Biometrics repository provider.
|
||||||
|
*/
|
||||||
|
public abstract BiometricsRepositoryProvider getBiometricsRepositoryProvider();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets implementation for the WifiTrackerLib.
|
* Gets implementation for the WifiTrackerLib.
|
||||||
*/
|
*/
|
||||||
|
@@ -37,6 +37,8 @@ import com.android.settings.aware.AwareFeatureProvider;
|
|||||||
import com.android.settings.aware.AwareFeatureProviderImpl;
|
import com.android.settings.aware.AwareFeatureProviderImpl;
|
||||||
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
||||||
import com.android.settings.biometrics.face.FaceFeatureProviderImpl;
|
import com.android.settings.biometrics.face.FaceFeatureProviderImpl;
|
||||||
|
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
|
||||||
|
import com.android.settings.biometrics2.factory.BiometricsRepositoryProviderImpl;
|
||||||
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
||||||
import com.android.settings.bluetooth.BluetoothFeatureProviderImpl;
|
import com.android.settings.bluetooth.BluetoothFeatureProviderImpl;
|
||||||
import com.android.settings.connecteddevice.dock.DockUpdaterFeatureProviderImpl;
|
import com.android.settings.connecteddevice.dock.DockUpdaterFeatureProviderImpl;
|
||||||
@@ -104,6 +106,7 @@ public class FeatureFactoryImpl extends FeatureFactory {
|
|||||||
private BluetoothFeatureProvider mBluetoothFeatureProvider;
|
private BluetoothFeatureProvider mBluetoothFeatureProvider;
|
||||||
private AwareFeatureProvider mAwareFeatureProvider;
|
private AwareFeatureProvider mAwareFeatureProvider;
|
||||||
private FaceFeatureProvider mFaceFeatureProvider;
|
private FaceFeatureProvider mFaceFeatureProvider;
|
||||||
|
private BiometricsRepositoryProvider mBiometricsRepositoryProvider;
|
||||||
private WifiTrackerLibProvider mWifiTrackerLibProvider;
|
private WifiTrackerLibProvider mWifiTrackerLibProvider;
|
||||||
private SecuritySettingsFeatureProvider mSecuritySettingsFeatureProvider;
|
private SecuritySettingsFeatureProvider mSecuritySettingsFeatureProvider;
|
||||||
private AccessibilitySearchFeatureProvider mAccessibilitySearchFeatureProvider;
|
private AccessibilitySearchFeatureProvider mAccessibilitySearchFeatureProvider;
|
||||||
@@ -305,6 +308,14 @@ public class FeatureFactoryImpl extends FeatureFactory {
|
|||||||
return mFaceFeatureProvider;
|
return mFaceFeatureProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BiometricsRepositoryProvider getBiometricsRepositoryProvider() {
|
||||||
|
if (mBiometricsRepositoryProvider == null) {
|
||||||
|
mBiometricsRepositoryProvider = new BiometricsRepositoryProviderImpl();
|
||||||
|
}
|
||||||
|
return mBiometricsRepositoryProvider;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WifiTrackerLibProvider getWifiTrackerLibProvider() {
|
public WifiTrackerLibProvider getWifiTrackerLibProvider() {
|
||||||
if (mWifiTrackerLibProvider == null) {
|
if (mWifiTrackerLibProvider == null) {
|
||||||
|
@@ -27,6 +27,7 @@ import com.android.settings.accounts.AccountFeatureProvider;
|
|||||||
import com.android.settings.applications.ApplicationFeatureProvider;
|
import com.android.settings.applications.ApplicationFeatureProvider;
|
||||||
import com.android.settings.aware.AwareFeatureProvider;
|
import com.android.settings.aware.AwareFeatureProvider;
|
||||||
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
||||||
|
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
|
||||||
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
||||||
import com.android.settings.dashboard.DashboardFeatureProvider;
|
import com.android.settings.dashboard.DashboardFeatureProvider;
|
||||||
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
|
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
|
||||||
@@ -78,6 +79,7 @@ public class FakeFeatureFactory extends FeatureFactory {
|
|||||||
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
|
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
|
||||||
public final AwareFeatureProvider mAwareFeatureProvider;
|
public final AwareFeatureProvider mAwareFeatureProvider;
|
||||||
public final FaceFeatureProvider mFaceFeatureProvider;
|
public final FaceFeatureProvider mFaceFeatureProvider;
|
||||||
|
public final BiometricsRepositoryProvider mBiometricsRepositoryProvider;
|
||||||
|
|
||||||
public PanelFeatureProvider panelFeatureProvider;
|
public PanelFeatureProvider panelFeatureProvider;
|
||||||
public SlicesFeatureProvider slicesFeatureProvider;
|
public SlicesFeatureProvider slicesFeatureProvider;
|
||||||
@@ -134,6 +136,7 @@ public class FakeFeatureFactory extends FeatureFactory {
|
|||||||
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
|
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
|
||||||
mAwareFeatureProvider = mock(AwareFeatureProvider.class);
|
mAwareFeatureProvider = mock(AwareFeatureProvider.class);
|
||||||
mFaceFeatureProvider = mock(FaceFeatureProvider.class);
|
mFaceFeatureProvider = mock(FaceFeatureProvider.class);
|
||||||
|
mBiometricsRepositoryProvider = mock(BiometricsRepositoryProvider.class);
|
||||||
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
|
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
|
||||||
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
|
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
|
||||||
mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
|
mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
|
||||||
@@ -256,6 +259,11 @@ public class FakeFeatureFactory extends FeatureFactory {
|
|||||||
return mFaceFeatureProvider;
|
return mFaceFeatureProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BiometricsRepositoryProvider getBiometricsRepositoryProvider() {
|
||||||
|
return mBiometricsRepositoryProvider;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WifiTrackerLibProvider getWifiTrackerLibProvider() {
|
public WifiTrackerLibProvider getWifiTrackerLibProvider() {
|
||||||
return wifiTrackerLibProvider;
|
return wifiTrackerLibProvider;
|
||||||
|
@@ -23,6 +23,7 @@ import com.android.settings.accounts.AccountFeatureProvider
|
|||||||
import com.android.settings.applications.ApplicationFeatureProvider
|
import com.android.settings.applications.ApplicationFeatureProvider
|
||||||
import com.android.settings.aware.AwareFeatureProvider
|
import com.android.settings.aware.AwareFeatureProvider
|
||||||
import com.android.settings.biometrics.face.FaceFeatureProvider
|
import com.android.settings.biometrics.face.FaceFeatureProvider
|
||||||
|
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider
|
||||||
import com.android.settings.bluetooth.BluetoothFeatureProvider
|
import com.android.settings.bluetooth.BluetoothFeatureProvider
|
||||||
import com.android.settings.dashboard.DashboardFeatureProvider
|
import com.android.settings.dashboard.DashboardFeatureProvider
|
||||||
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider
|
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider
|
||||||
@@ -153,6 +154,10 @@ class FakeFeatureFactory : FeatureFactory() {
|
|||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getBiometricsRepositoryProvider(): BiometricsRepositoryProvider {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
override fun getWifiTrackerLibProvider(): WifiTrackerLibProvider {
|
override fun getWifiTrackerLibProvider(): WifiTrackerLibProvider {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* 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.data.repository;
|
||||||
|
|
||||||
|
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_HOME_BUTTON;
|
||||||
|
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
|
||||||
|
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_ULTRASONIC;
|
||||||
|
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 org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.settings.testutils.ResourcesUtils;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoRule;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class FingerprintRepositoryTest {
|
||||||
|
|
||||||
|
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
||||||
|
|
||||||
|
@Mock private Resources mResources;
|
||||||
|
@Mock private FingerprintManager mFingerprintManager;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private FingerprintRepository mFingerprintRepository;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mContext = ApplicationProvider.getApplicationContext();
|
||||||
|
mFingerprintRepository = new FingerprintRepository(mFingerprintManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCanAssumeSensorType() {
|
||||||
|
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 1);
|
||||||
|
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
|
||||||
|
|
||||||
|
setupFingerprintFirstSensor(mFingerprintManager, TYPE_REAR, 1);
|
||||||
|
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
|
||||||
|
|
||||||
|
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_ULTRASONIC, 1);
|
||||||
|
assertThat(mFingerprintRepository.canAssumeUdfps()).isTrue();
|
||||||
|
|
||||||
|
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_OPTICAL, 1);
|
||||||
|
assertThat(mFingerprintRepository.canAssumeUdfps()).isTrue();
|
||||||
|
|
||||||
|
setupFingerprintFirstSensor(mFingerprintManager, TYPE_POWER_BUTTON, 1);
|
||||||
|
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
|
||||||
|
|
||||||
|
setupFingerprintFirstSensor(mFingerprintManager, TYPE_HOME_BUTTON, 1);
|
||||||
|
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMaxFingerprints() {
|
||||||
|
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 44);
|
||||||
|
assertThat(mFingerprintRepository.getMaxFingerprints()).isEqualTo(44);
|
||||||
|
|
||||||
|
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 999);
|
||||||
|
assertThat(mFingerprintRepository.getMaxFingerprints()).isEqualTo(999);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNumOfEnrolledFingerprintsSize() {
|
||||||
|
setupFingerprintEnrolledFingerprints(mFingerprintManager, 10, 3);
|
||||||
|
setupFingerprintEnrolledFingerprints(mFingerprintManager, 22, 99);
|
||||||
|
|
||||||
|
assertThat(mFingerprintRepository.getNumOfEnrolledFingerprintsSize(10)).isEqualTo(3);
|
||||||
|
assertThat(mFingerprintRepository.getNumOfEnrolledFingerprintsSize(22)).isEqualTo(99);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMaxFingerprintsInSuw() {
|
||||||
|
setupSuwMaxFingerprintsEnrollable(mContext, mResources, 333);
|
||||||
|
assertThat(mFingerprintRepository.getMaxFingerprintsInSuw(mResources))
|
||||||
|
.isEqualTo(333);
|
||||||
|
|
||||||
|
setupSuwMaxFingerprintsEnrollable(mContext, mResources, 20);
|
||||||
|
assertThat(mFingerprintRepository.getMaxFingerprintsInSuw(mResources)).isEqualTo(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setupSuwMaxFingerprintsEnrollable(
|
||||||
|
@NonNull Context context,
|
||||||
|
@NonNull Resources mockedResources,
|
||||||
|
int numOfFp) {
|
||||||
|
final int resId = ResourcesUtils.getResourcesId(context, "integer",
|
||||||
|
"suw_max_fingerprints_enrollable");
|
||||||
|
when(mockedResources.getInteger(resId)).thenReturn(numOfFp);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,380 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
|
||||||
|
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
|
||||||
|
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
|
||||||
|
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
|
||||||
|
|
||||||
|
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE;
|
||||||
|
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_SENSOR_ID;
|
||||||
|
import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_CHALLENGE;
|
||||||
|
import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_SENSOR_ID;
|
||||||
|
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CHOOSE_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.ChallengeGenerator;
|
||||||
|
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CredentialAction;
|
||||||
|
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.GenerateChallengeCallback;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResult;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.internal.widget.LockPatternUtils;
|
||||||
|
import com.android.internal.widget.VerifyCredentialResponse;
|
||||||
|
import com.android.settings.biometrics2.ui.model.CredentialModel;
|
||||||
|
import com.android.settings.password.ChooseLockPattern;
|
||||||
|
import com.android.settings.password.ChooseLockSettingsHelper;
|
||||||
|
import com.android.settings.testutils.InstantTaskExecutorRule;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoRule;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class AutoCredentialViewModelTest {
|
||||||
|
|
||||||
|
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
||||||
|
@Rule public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();
|
||||||
|
|
||||||
|
@Mock private LifecycleOwner mLifecycleOwner;
|
||||||
|
@Mock private LockPatternUtils mLockPatternUtils;
|
||||||
|
private TestChallengeGenerator mChallengeGenerator = null;
|
||||||
|
private AutoCredentialViewModel mAutoCredentialViewModel;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mChallengeGenerator = new TestChallengeGenerator();
|
||||||
|
mAutoCredentialViewModel = new AutoCredentialViewModel(
|
||||||
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
mLockPatternUtils,
|
||||||
|
mChallengeGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CredentialModel newCredentialModel(int userId, long challenge,
|
||||||
|
@Nullable byte[] token, long gkPwHandle) {
|
||||||
|
final Intent intent = new Intent();
|
||||||
|
intent.putExtra(Intent.EXTRA_USER_ID, userId);
|
||||||
|
intent.putExtra(EXTRA_KEY_SENSOR_ID, 1);
|
||||||
|
intent.putExtra(EXTRA_KEY_CHALLENGE, challenge);
|
||||||
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
|
||||||
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
|
||||||
|
return new CredentialModel(intent, SystemClock.elapsedRealtimeClock());
|
||||||
|
}
|
||||||
|
|
||||||
|
private CredentialModel newValidTokenCredentialModel(int userId) {
|
||||||
|
return newCredentialModel(userId, 1L, new byte[] { 0 }, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CredentialModel newInvalidChallengeCredentialModel(int userId) {
|
||||||
|
return newCredentialModel(userId, INVALID_CHALLENGE, null, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CredentialModel newGkPwHandleCredentialModel(int userId, long gkPwHandle) {
|
||||||
|
return newCredentialModel(userId, INVALID_CHALLENGE, null, gkPwHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyNothingHappen() {
|
||||||
|
assertThat(mAutoCredentialViewModel.getActionLiveData().getValue()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyOnlyActionLiveData(@CredentialAction int action) {
|
||||||
|
final Integer value = mAutoCredentialViewModel.getActionLiveData().getValue();
|
||||||
|
assertThat(value).isEqualTo(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupGenerateTokenFlow(long gkPwHandle, int userId, int newSensorId,
|
||||||
|
long newChallenge) {
|
||||||
|
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
||||||
|
PASSWORD_QUALITY_SOMETHING);
|
||||||
|
mChallengeGenerator.mUserId = userId;
|
||||||
|
mChallengeGenerator.mSensorId = newSensorId;
|
||||||
|
mChallengeGenerator.mChallenge = newChallenge;
|
||||||
|
when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
|
||||||
|
.thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkCredential_validCredentialCase() {
|
||||||
|
final int userId = 99;
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(newValidTokenCredentialModel(userId));
|
||||||
|
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
||||||
|
PASSWORD_QUALITY_SOMETHING);
|
||||||
|
|
||||||
|
// Run credential check
|
||||||
|
mAutoCredentialViewModel.onCreate(mLifecycleOwner);
|
||||||
|
|
||||||
|
verifyNothingHappen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkCredential_needToChooseLock() {
|
||||||
|
final int userId = 100;
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId));
|
||||||
|
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
||||||
|
PASSWORD_QUALITY_UNSPECIFIED);
|
||||||
|
|
||||||
|
// Run credential check
|
||||||
|
mAutoCredentialViewModel.onCreate(mLifecycleOwner);
|
||||||
|
|
||||||
|
verifyOnlyActionLiveData(CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkCredential_needToConfirmLockFoSomething() {
|
||||||
|
final int userId = 101;
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId));
|
||||||
|
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
||||||
|
PASSWORD_QUALITY_SOMETHING);
|
||||||
|
|
||||||
|
// Run credential check
|
||||||
|
mAutoCredentialViewModel.onCreate(mLifecycleOwner);
|
||||||
|
|
||||||
|
verifyOnlyActionLiveData(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkCredential_needToConfirmLockForNumeric() {
|
||||||
|
final int userId = 102;
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId));
|
||||||
|
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
||||||
|
PASSWORD_QUALITY_NUMERIC);
|
||||||
|
|
||||||
|
// Run credential check
|
||||||
|
mAutoCredentialViewModel.onCreate(mLifecycleOwner);
|
||||||
|
|
||||||
|
verifyOnlyActionLiveData(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkCredential_needToConfirmLockForAlphabetic() {
|
||||||
|
final int userId = 103;
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId));
|
||||||
|
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
||||||
|
PASSWORD_QUALITY_ALPHABETIC);
|
||||||
|
|
||||||
|
// Run credential check
|
||||||
|
mAutoCredentialViewModel.onCreate(mLifecycleOwner);
|
||||||
|
|
||||||
|
verifyOnlyActionLiveData(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkCredential_generateChallenge() {
|
||||||
|
final int userId = 104;
|
||||||
|
final long gkPwHandle = 1111L;
|
||||||
|
final CredentialModel credentialModel = newGkPwHandleCredentialModel(userId, gkPwHandle);
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(credentialModel);
|
||||||
|
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
||||||
|
PASSWORD_QUALITY_SOMETHING);
|
||||||
|
|
||||||
|
final int newSensorId = 10;
|
||||||
|
final long newChallenge = 20L;
|
||||||
|
setupGenerateTokenFlow(gkPwHandle, userId, newSensorId, newChallenge);
|
||||||
|
|
||||||
|
// Run credential check
|
||||||
|
mAutoCredentialViewModel.onCreate(mLifecycleOwner);
|
||||||
|
|
||||||
|
assertThat(mAutoCredentialViewModel.getActionLiveData().getValue()).isNull();
|
||||||
|
assertThat(credentialModel.getSensorId()).isEqualTo(newSensorId);
|
||||||
|
assertThat(credentialModel.getChallenge()).isEqualTo(newChallenge);
|
||||||
|
assertThat(CredentialModel.isValidToken(credentialModel.getToken())).isTrue();
|
||||||
|
assertThat(CredentialModel.isValidGkPwHandle(credentialModel.getGkPwHandle())).isFalse();
|
||||||
|
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetUserId() {
|
||||||
|
final int userId = 106;
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId));
|
||||||
|
|
||||||
|
// Get userId
|
||||||
|
assertThat(mAutoCredentialViewModel.getUserId()).isEqualTo(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckNewCredentialFromActivityResult_invalidChooseLock() {
|
||||||
|
final int userId = 107;
|
||||||
|
final long gkPwHandle = 3333L;
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(
|
||||||
|
newGkPwHandleCredentialModel(userId, gkPwHandle));
|
||||||
|
final Intent intent = new Intent();
|
||||||
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
|
||||||
|
|
||||||
|
// run checkNewCredentialFromActivityResult()
|
||||||
|
final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(true,
|
||||||
|
new ActivityResult(ChooseLockPattern.RESULT_FINISHED + 1, intent));
|
||||||
|
|
||||||
|
assertThat(ret).isFalse();
|
||||||
|
verifyNothingHappen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckNewCredentialFromActivityResult_invalidConfirmLock() {
|
||||||
|
final int userId = 107;
|
||||||
|
final long gkPwHandle = 3333L;
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(
|
||||||
|
newGkPwHandleCredentialModel(userId, gkPwHandle));
|
||||||
|
final Intent intent = new Intent();
|
||||||
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
|
||||||
|
|
||||||
|
// run checkNewCredentialFromActivityResult()
|
||||||
|
final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(false,
|
||||||
|
new ActivityResult(Activity.RESULT_OK + 1, intent));
|
||||||
|
|
||||||
|
assertThat(ret).isFalse();
|
||||||
|
verifyNothingHappen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckNewCredentialFromActivityResult_nullDataChooseLock() {
|
||||||
|
final int userId = 108;
|
||||||
|
final long gkPwHandle = 4444L;
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(
|
||||||
|
newGkPwHandleCredentialModel(userId, gkPwHandle));
|
||||||
|
|
||||||
|
// run checkNewCredentialFromActivityResult()
|
||||||
|
final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(true,
|
||||||
|
new ActivityResult(ChooseLockPattern.RESULT_FINISHED, null));
|
||||||
|
|
||||||
|
assertThat(ret).isFalse();
|
||||||
|
verifyNothingHappen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckNewCredentialFromActivityResult_nullDataConfirmLock() {
|
||||||
|
final int userId = 109;
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(newInvalidChallengeCredentialModel(userId));
|
||||||
|
|
||||||
|
// run checkNewCredentialFromActivityResult()
|
||||||
|
final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(false,
|
||||||
|
new ActivityResult(Activity.RESULT_OK, null));
|
||||||
|
|
||||||
|
assertThat(ret).isFalse();
|
||||||
|
verifyNothingHappen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckNewCredentialFromActivityResult_validChooseLock() {
|
||||||
|
final int userId = 108;
|
||||||
|
final CredentialModel credentialModel = newInvalidChallengeCredentialModel(userId);
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(credentialModel);
|
||||||
|
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
||||||
|
PASSWORD_QUALITY_SOMETHING);
|
||||||
|
|
||||||
|
final long gkPwHandle = 6666L;
|
||||||
|
final int newSensorId = 50;
|
||||||
|
final long newChallenge = 60L;
|
||||||
|
setupGenerateTokenFlow(gkPwHandle, userId, newSensorId, newChallenge);
|
||||||
|
final Intent intent = new Intent();
|
||||||
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
|
||||||
|
|
||||||
|
// Run checkNewCredentialFromActivityResult()
|
||||||
|
final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(true,
|
||||||
|
new ActivityResult(ChooseLockPattern.RESULT_FINISHED, intent));
|
||||||
|
|
||||||
|
assertThat(ret).isTrue();
|
||||||
|
assertThat(mAutoCredentialViewModel.getActionLiveData().getValue()).isNull();
|
||||||
|
assertThat(credentialModel.getSensorId()).isEqualTo(newSensorId);
|
||||||
|
assertThat(credentialModel.getChallenge()).isEqualTo(newChallenge);
|
||||||
|
assertThat(CredentialModel.isValidToken(credentialModel.getToken())).isTrue();
|
||||||
|
assertThat(CredentialModel.isValidGkPwHandle(credentialModel.getGkPwHandle())).isFalse();
|
||||||
|
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckNewCredentialFromActivityResult_validConfirmLock() {
|
||||||
|
final int userId = 109;
|
||||||
|
final CredentialModel credentialModel = newInvalidChallengeCredentialModel(userId);
|
||||||
|
mAutoCredentialViewModel.setCredentialModel(credentialModel);
|
||||||
|
when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
||||||
|
PASSWORD_QUALITY_SOMETHING);
|
||||||
|
|
||||||
|
final long gkPwHandle = 5555L;
|
||||||
|
final int newSensorId = 80;
|
||||||
|
final long newChallenge = 90L;
|
||||||
|
setupGenerateTokenFlow(gkPwHandle, userId, newSensorId, newChallenge);
|
||||||
|
final Intent intent = new Intent();
|
||||||
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
|
||||||
|
|
||||||
|
// Run checkNewCredentialFromActivityResult()
|
||||||
|
final boolean ret = mAutoCredentialViewModel.checkNewCredentialFromActivityResult(false,
|
||||||
|
new ActivityResult(Activity.RESULT_OK, intent));
|
||||||
|
|
||||||
|
assertThat(ret).isTrue();
|
||||||
|
assertThat(mAutoCredentialViewModel.getActionLiveData().getValue()).isNull();
|
||||||
|
assertThat(credentialModel.getSensorId()).isEqualTo(newSensorId);
|
||||||
|
assertThat(credentialModel.getChallenge()).isEqualTo(newChallenge);
|
||||||
|
assertThat(CredentialModel.isValidToken(credentialModel.getToken())).isTrue();
|
||||||
|
assertThat(CredentialModel.isValidGkPwHandle(credentialModel.getGkPwHandle())).isFalse();
|
||||||
|
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestChallengeGenerator implements ChallengeGenerator {
|
||||||
|
public int mSensorId = INVALID_SENSOR_ID;
|
||||||
|
public int mUserId = UserHandle.myUserId();
|
||||||
|
public long mChallenge = INVALID_CHALLENGE;
|
||||||
|
public int mCallbackRunCount = 0;
|
||||||
|
private GenerateChallengeCallback mCallback;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public GenerateChallengeCallback getCallback() {
|
||||||
|
return mCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCallback(@Nullable GenerateChallengeCallback callback) {
|
||||||
|
mCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void generateChallenge(int userId) {
|
||||||
|
final GenerateChallengeCallback callback = mCallback;
|
||||||
|
if (callback == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback.onChallengeGenerated(mSensorId, mUserId, mChallenge);
|
||||||
|
++mCallbackRunCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private VerifyCredentialResponse newGoodCredential(long gkPwHandle, @NonNull byte[] hat) {
|
||||||
|
return new VerifyCredentialResponse.Builder()
|
||||||
|
.setGatekeeperPasswordHandle(gkPwHandle)
|
||||||
|
.setGatekeeperHAT(hat)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
* 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.fingerprint.FingerprintSensorProperties.TYPE_REAR;
|
||||||
|
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
|
||||||
|
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
|
||||||
|
|
||||||
|
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_OK;
|
||||||
|
import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus.FINGERPRINT_ENROLLABLE_UNKNOWN;
|
||||||
|
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_SKIP_OR_CANCEL;
|
||||||
|
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newAllFalseRequest;
|
||||||
|
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwDeferredRequest;
|
||||||
|
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.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 org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||||
|
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
|
||||||
|
import com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus;
|
||||||
|
import com.android.settings.testutils.InstantTaskExecutorRule;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoRule;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class FingerprintEnrollIntroViewModelTest {
|
||||||
|
|
||||||
|
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
||||||
|
@Rule public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();
|
||||||
|
|
||||||
|
@Mock private Resources mResources;
|
||||||
|
@Mock private LifecycleOwner mLifecycleOwner;
|
||||||
|
@Mock private FingerprintManager mFingerprintManager;
|
||||||
|
|
||||||
|
private Application mApplication;
|
||||||
|
private FingerprintRepository mFingerprintRepository;
|
||||||
|
private FingerprintEnrollIntroViewModel mViewModel;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mApplication = ApplicationProvider.getApplicationContext();
|
||||||
|
mFingerprintRepository = new FingerprintRepository(mFingerprintManager);
|
||||||
|
mViewModel = new FingerprintEnrollIntroViewModel(mApplication, mFingerprintRepository);
|
||||||
|
// MediatorLiveData won't update itself unless observed
|
||||||
|
mViewModel.getPageStatusLiveData().observeForever(event -> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPageStatusLiveDataDefaultValue() {
|
||||||
|
final FingerprintEnrollIntroStatus status = mViewModel.getPageStatusLiveData().getValue();
|
||||||
|
assertThat(status.hasScrollToBottom()).isFalse();
|
||||||
|
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetEnrollmentRequest() {
|
||||||
|
final EnrollmentRequest request = newAllFalseRequest(mApplication);
|
||||||
|
|
||||||
|
mViewModel.setEnrollmentRequest(request);
|
||||||
|
|
||||||
|
assertThat(mViewModel.getEnrollmentRequest()).isEqualTo(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnStartToUpdateEnrollableStatus_isSuw() {
|
||||||
|
final int userId = 44;
|
||||||
|
mViewModel.setUserId(userId);
|
||||||
|
mViewModel.setEnrollmentRequest(newIsSuwRequest(mApplication));
|
||||||
|
|
||||||
|
setupFingerprintEnrolledFingerprints(mFingerprintManager, userId, 0);
|
||||||
|
setupSuwMaxFingerprintsEnrollable(mApplication, mResources, 1);
|
||||||
|
mViewModel.onStart(mLifecycleOwner);
|
||||||
|
FingerprintEnrollIntroStatus status = mViewModel.getPageStatusLiveData().getValue();
|
||||||
|
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
|
||||||
|
|
||||||
|
setupFingerprintEnrolledFingerprints(mFingerprintManager, userId, 1);
|
||||||
|
setupSuwMaxFingerprintsEnrollable(mApplication, mResources, 1);
|
||||||
|
mViewModel.onStart(mLifecycleOwner);
|
||||||
|
status = mViewModel.getPageStatusLiveData().getValue();
|
||||||
|
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnStartToUpdateEnrollableStatus_isNotSuw() {
|
||||||
|
testOnStartToUpdateEnrollableStatus(newAllFalseRequest(mApplication));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnStartToUpdateEnrollableStatus_isSuwDeferred() {
|
||||||
|
testOnStartToUpdateEnrollableStatus(newIsSuwDeferredRequest(mApplication));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnStartToUpdateEnrollableStatus_isSuwPortal() {
|
||||||
|
testOnStartToUpdateEnrollableStatus(newIsSuwPortalRequest(mApplication));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnStartToUpdateEnrollableStatus_isSuwSuggestedActionFlow() {
|
||||||
|
testOnStartToUpdateEnrollableStatus(newIsSuwSuggestedActionFlowRequest(mApplication));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testOnStartToUpdateEnrollableStatus(@NonNull EnrollmentRequest request) {
|
||||||
|
final int userId = 45;
|
||||||
|
mViewModel.setUserId(userId);
|
||||||
|
mViewModel.setEnrollmentRequest(request);
|
||||||
|
|
||||||
|
setupFingerprintEnrolledFingerprints(mFingerprintManager, userId, 0);
|
||||||
|
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5);
|
||||||
|
mViewModel.onStart(mLifecycleOwner);
|
||||||
|
FingerprintEnrollIntroStatus status = mViewModel.getPageStatusLiveData().getValue();
|
||||||
|
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
|
||||||
|
|
||||||
|
setupFingerprintEnrolledFingerprints(mFingerprintManager, userId, 5);
|
||||||
|
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5);
|
||||||
|
mViewModel.onStart(mLifecycleOwner);
|
||||||
|
status = mViewModel.getPageStatusLiveData().getValue();
|
||||||
|
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void textCanAssumeUdfps() {
|
||||||
|
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_ULTRASONIC, 1);
|
||||||
|
assertThat(mViewModel.canAssumeUdfps()).isEqualTo(true);
|
||||||
|
|
||||||
|
setupFingerprintFirstSensor(mFingerprintManager, TYPE_REAR, 1);
|
||||||
|
assertThat(mViewModel.canAssumeUdfps()).isEqualTo(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsParentalConsentRequired() {
|
||||||
|
// We shall not mock FingerprintRepository, but
|
||||||
|
// FingerprintRepository.isParentalConsentRequired() calls static method inside, we can't
|
||||||
|
// mock static method
|
||||||
|
final FingerprintRepository fingerprintRepository = mock(FingerprintRepository.class);
|
||||||
|
mViewModel = new FingerprintEnrollIntroViewModel(mApplication, fingerprintRepository);
|
||||||
|
|
||||||
|
when(fingerprintRepository.isParentalConsentRequired(mApplication)).thenReturn(true);
|
||||||
|
assertThat(mViewModel.isParentalConsentRequired()).isEqualTo(true);
|
||||||
|
|
||||||
|
when(fingerprintRepository.isParentalConsentRequired(mApplication)).thenReturn(false);
|
||||||
|
assertThat(mViewModel.isParentalConsentRequired()).isEqualTo(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsBiometricUnlockDisabledByAdmin() {
|
||||||
|
// We shall not mock FingerprintRepository, but
|
||||||
|
// FingerprintRepository.isDisabledByAdmin() calls static method inside, we can't mock
|
||||||
|
// static method
|
||||||
|
final FingerprintRepository fingerprintRepository = mock(FingerprintRepository.class);
|
||||||
|
mViewModel = new FingerprintEnrollIntroViewModel(mApplication, fingerprintRepository);
|
||||||
|
|
||||||
|
final int userId = 33;
|
||||||
|
mViewModel.setUserId(userId);
|
||||||
|
|
||||||
|
when(fingerprintRepository.isDisabledByAdmin(mApplication, userId)).thenReturn(true);
|
||||||
|
assertThat(mViewModel.isBiometricUnlockDisabledByAdmin()).isEqualTo(true);
|
||||||
|
|
||||||
|
when(fingerprintRepository.isDisabledByAdmin(mApplication, userId)).thenReturn(false);
|
||||||
|
assertThat(mViewModel.isBiometricUnlockDisabledByAdmin()).isEqualTo(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetHasScrolledToBottom() {
|
||||||
|
mViewModel.setHasScrolledToBottom();
|
||||||
|
|
||||||
|
FingerprintEnrollIntroStatus status = mViewModel.getPageStatusLiveData().getValue();
|
||||||
|
|
||||||
|
assertThat(status.hasScrollToBottom()).isEqualTo(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnNextButtonClick_enrollNext() {
|
||||||
|
final int userId = 46;
|
||||||
|
mViewModel.setUserId(userId);
|
||||||
|
mViewModel.setEnrollmentRequest(newIsSuwRequest(mApplication));
|
||||||
|
|
||||||
|
// Set latest status to FINGERPRINT_ENROLLABLE_OK
|
||||||
|
setupFingerprintEnrolledFingerprints(mFingerprintManager, userId, 0);
|
||||||
|
setupSuwMaxFingerprintsEnrollable(mApplication, mResources, 1);
|
||||||
|
mViewModel.onStart(mLifecycleOwner);
|
||||||
|
FingerprintEnrollIntroStatus status = mViewModel.getPageStatusLiveData().getValue();
|
||||||
|
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
|
||||||
|
|
||||||
|
// Perform click on `next`
|
||||||
|
mViewModel.onNextButtonClick(null);
|
||||||
|
|
||||||
|
assertThat(mViewModel.getActionLiveData().getValue())
|
||||||
|
.isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnNextButtonClick_doneAndFinish() {
|
||||||
|
final int userId = 46;
|
||||||
|
mViewModel.setUserId(userId);
|
||||||
|
mViewModel.setEnrollmentRequest(newIsSuwRequest(mApplication));
|
||||||
|
|
||||||
|
// Set latest status to FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
||||||
|
setupFingerprintEnrolledFingerprints(mFingerprintManager, userId, 1);
|
||||||
|
setupSuwMaxFingerprintsEnrollable(mApplication, mResources, 1);
|
||||||
|
mViewModel.onStart(mLifecycleOwner);
|
||||||
|
FingerprintEnrollIntroStatus status = mViewModel.getPageStatusLiveData().getValue();
|
||||||
|
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX);
|
||||||
|
|
||||||
|
// Perform click on `next`
|
||||||
|
mViewModel.onNextButtonClick(null);
|
||||||
|
|
||||||
|
assertThat(mViewModel.getActionLiveData().getValue())
|
||||||
|
.isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnSkipOrCancelButtonClick() {
|
||||||
|
mViewModel.onSkipOrCancelButtonClick(null);
|
||||||
|
|
||||||
|
assertThat(mViewModel.getActionLiveData().getValue())
|
||||||
|
.isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,252 @@
|
|||||||
|
/*
|
||||||
|
* 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.biometrics.BiometricEnrollBase.RESULT_FINISHED;
|
||||||
|
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_SKIP;
|
||||||
|
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.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.newIsSuwRequest;
|
||||||
|
import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintEnrolledFingerprints;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.app.KeyguardManager;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResult;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||||
|
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
|
||||||
|
import com.android.settings.password.SetupSkipDialog;
|
||||||
|
import com.android.settings.testutils.InstantTaskExecutorRule;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoRule;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class FingerprintEnrollmentViewModelTest {
|
||||||
|
|
||||||
|
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
||||||
|
@Rule public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();
|
||||||
|
|
||||||
|
@Mock private FingerprintManager mFingerprintManager;
|
||||||
|
@Mock private KeyguardManager mKeyguardManager;
|
||||||
|
|
||||||
|
private Application mApplication;
|
||||||
|
private FingerprintRepository mFingerprintRepository;
|
||||||
|
private FingerprintEnrollmentViewModel mViewModel;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mApplication = ApplicationProvider.getApplicationContext();
|
||||||
|
mFingerprintRepository = new FingerprintRepository(mFingerprintManager);
|
||||||
|
mViewModel = new FingerprintEnrollmentViewModel(mApplication, mFingerprintRepository,
|
||||||
|
mKeyguardManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetRequest() {
|
||||||
|
when(mKeyguardManager.isKeyguardSecure()).thenReturn(true);
|
||||||
|
assertThat(mViewModel.getRequest()).isNull();
|
||||||
|
|
||||||
|
final EnrollmentRequest request = newAllFalseRequest(mApplication);
|
||||||
|
mViewModel.setRequest(request);
|
||||||
|
assertThat(mViewModel.getRequest()).isEqualTo(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNextActivityBaseIntentExtras() {
|
||||||
|
mViewModel.setRequest(newAllFalseRequest(mApplication));
|
||||||
|
assertThat(mViewModel.getNextActivityBaseIntentExtras()).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnContinueEnrollActivityResult_shouldRelaySkip1Result() {
|
||||||
|
mViewModel.setRequest(newAllFalseRequest(mApplication));
|
||||||
|
final ActivityResult result = new ActivityResult(RESULT_SKIP, null);
|
||||||
|
|
||||||
|
// Run onContinueEnrollActivityResult
|
||||||
|
mViewModel.onContinueEnrollActivityResult(result, 100);
|
||||||
|
|
||||||
|
assertThat(mViewModel.getSetResultLiveData().getValue()).isEqualTo(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnContinueEnrollActivityResult_shouldRelaySkip2Result() {
|
||||||
|
mViewModel.setRequest(newAllFalseRequest(mApplication));
|
||||||
|
final ActivityResult result = new ActivityResult(SetupSkipDialog.RESULT_SKIP, null);
|
||||||
|
|
||||||
|
// Run onContinueEnrollActivityResult
|
||||||
|
mViewModel.onContinueEnrollActivityResult(result, 100);
|
||||||
|
|
||||||
|
assertThat(mViewModel.getSetResultLiveData().getValue()).isEqualTo(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnContinueEnrollActivityResult_shouldRelayNullDataTimeoutResult() {
|
||||||
|
mViewModel.setRequest(newAllFalseRequest(mApplication));
|
||||||
|
final ActivityResult result = new ActivityResult(RESULT_TIMEOUT, null);
|
||||||
|
|
||||||
|
// Run onContinueEnrollActivityResult
|
||||||
|
mViewModel.onContinueEnrollActivityResult(result, 100);
|
||||||
|
final ActivityResult setResult = mViewModel.getSetResultLiveData().getValue();
|
||||||
|
|
||||||
|
assertThat(setResult).isNotNull();
|
||||||
|
assertThat(setResult.getResultCode()).isEqualTo(result.getResultCode());
|
||||||
|
assertThat(setResult.getData()).isEqualTo(result.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnContinueEnrollActivityResult_shouldRelayWithDataTimeoutResult() {
|
||||||
|
mViewModel.setRequest(newAllFalseRequest(mApplication));
|
||||||
|
final Intent intent = new Intent("testAction");
|
||||||
|
intent.putExtra("testKey", "testValue");
|
||||||
|
final ActivityResult result = new ActivityResult(RESULT_TIMEOUT, intent);
|
||||||
|
|
||||||
|
// Run onContinueEnrollActivityResult
|
||||||
|
mViewModel.onContinueEnrollActivityResult(result, 100);
|
||||||
|
final ActivityResult setResult = mViewModel.getSetResultLiveData().getValue();
|
||||||
|
|
||||||
|
assertThat(setResult).isNotNull();
|
||||||
|
assertThat(setResult.getResultCode()).isEqualTo(result.getResultCode());
|
||||||
|
assertThat(setResult.getData()).isEqualTo(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnContinueEnrollActivityResult_shouldRelayNullDataFinishResult() {
|
||||||
|
mViewModel.setRequest(newAllFalseRequest(mApplication));
|
||||||
|
final ActivityResult result = new ActivityResult(RESULT_FINISHED, null);
|
||||||
|
|
||||||
|
// Run onContinueEnrollActivityResult
|
||||||
|
mViewModel.onContinueEnrollActivityResult(result, 100);
|
||||||
|
final ActivityResult setResult = mViewModel.getSetResultLiveData().getValue();
|
||||||
|
|
||||||
|
assertThat(setResult).isNotNull();
|
||||||
|
assertThat(setResult.getResultCode()).isEqualTo(result.getResultCode());
|
||||||
|
assertThat(setResult.getData()).isEqualTo(result.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnContinueEnrollActivityResult_shouldRelayWithDataFinishResult() {
|
||||||
|
mViewModel.setRequest(newAllFalseRequest(mApplication));
|
||||||
|
final Intent intent = new Intent("testAction");
|
||||||
|
intent.putExtra("testKey", "testValue");
|
||||||
|
final ActivityResult result = new ActivityResult(RESULT_FINISHED, intent);
|
||||||
|
|
||||||
|
// Run onContinueEnrollActivityResult
|
||||||
|
mViewModel.onContinueEnrollActivityResult(result, 100);
|
||||||
|
final ActivityResult setResult = mViewModel.getSetResultLiveData().getValue();
|
||||||
|
|
||||||
|
assertThat(setResult).isNotNull();
|
||||||
|
assertThat(setResult.getResultCode()).isEqualTo(result.getResultCode());
|
||||||
|
assertThat(setResult.getData()).isEqualTo(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnContinueEnrollActivityResult_shouldRelayNullDataFinishResultAsNewData() {
|
||||||
|
when(mKeyguardManager.isKeyguardSecure()).thenReturn(true);
|
||||||
|
final int userId = 111;
|
||||||
|
final int numOfFp = 4;
|
||||||
|
setupFingerprintEnrolledFingerprints(mFingerprintManager, userId, numOfFp);
|
||||||
|
mViewModel.setRequest(newIsSuwRequest(mApplication));
|
||||||
|
final ActivityResult result = new ActivityResult(RESULT_FINISHED, null);
|
||||||
|
|
||||||
|
// Run onContinueEnrollActivityResult
|
||||||
|
mViewModel.onContinueEnrollActivityResult(result, userId);
|
||||||
|
final ActivityResult setResult = mViewModel.getSetResultLiveData().getValue();
|
||||||
|
|
||||||
|
assertThat(setResult).isNotNull();
|
||||||
|
assertThat(setResult.getResultCode()).isEqualTo(result.getResultCode());
|
||||||
|
assertThat(setResult.getData()).isNotNull();
|
||||||
|
assertThat(setResult.getData().getExtras()).isNotNull();
|
||||||
|
assertThat(setResult.getData().getExtras().getInt(EXTRA_FINGERPRINT_ENROLLED_COUNT, -1))
|
||||||
|
.isEqualTo(numOfFp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnContinueEnrollActivityResult_shouldRelayWithDataFinishResultAsNewData() {
|
||||||
|
when(mKeyguardManager.isKeyguardSecure()).thenReturn(true);
|
||||||
|
final int userId = 20;
|
||||||
|
final int numOfFp = 9;
|
||||||
|
setupFingerprintEnrolledFingerprints(mFingerprintManager, userId, numOfFp);
|
||||||
|
mViewModel.setRequest(newIsSuwRequest(mApplication));
|
||||||
|
final String action = "testAction";
|
||||||
|
final String key = "testKey";
|
||||||
|
final String value = "testValue";
|
||||||
|
final Intent intent = new Intent(action);
|
||||||
|
intent.putExtra(key, value);
|
||||||
|
final ActivityResult result = new ActivityResult(RESULT_FINISHED, intent);
|
||||||
|
|
||||||
|
// Run onContinueEnrollActivityResult
|
||||||
|
mViewModel.onContinueEnrollActivityResult(result, userId);
|
||||||
|
final ActivityResult setResult = mViewModel.getSetResultLiveData().getValue();
|
||||||
|
|
||||||
|
assertThat(setResult).isNotNull();
|
||||||
|
assertThat(setResult.getResultCode()).isEqualTo(result.getResultCode());
|
||||||
|
assertThat(setResult.getData()).isNotNull();
|
||||||
|
assertThat(setResult.getData().getExtras()).isNotNull();
|
||||||
|
assertThat(setResult.getData().getExtras().getInt(EXTRA_FINGERPRINT_ENROLLED_COUNT, -1))
|
||||||
|
.isEqualTo(numOfFp);
|
||||||
|
assertThat(setResult.getData().getExtras().getString(key)).isEqualTo(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetSavedInstanceState() {
|
||||||
|
final Bundle bundle = new Bundle();
|
||||||
|
mViewModel.isWaitingActivityResult().set(true);
|
||||||
|
|
||||||
|
// setSavedInstanceState() as false
|
||||||
|
bundle.putBoolean(SAVED_STATE_IS_WAITING_ACTIVITY_RESULT, false);
|
||||||
|
mViewModel.setSavedInstanceState(bundle);
|
||||||
|
assertThat(mViewModel.isWaitingActivityResult().get()).isFalse();
|
||||||
|
|
||||||
|
// setSavedInstanceState() as false
|
||||||
|
bundle.putBoolean(SAVED_STATE_IS_WAITING_ACTIVITY_RESULT, true);
|
||||||
|
mViewModel.setSavedInstanceState(bundle);
|
||||||
|
assertThat(mViewModel.isWaitingActivityResult().get()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOnSaveInstanceState() {
|
||||||
|
final Bundle bundle = new Bundle();
|
||||||
|
|
||||||
|
// setSavedInstanceState() as false
|
||||||
|
mViewModel.isWaitingActivityResult().set(false);
|
||||||
|
mViewModel.onSaveInstanceState(bundle);
|
||||||
|
assertThat(bundle.getBoolean(SAVED_STATE_IS_WAITING_ACTIVITY_RESULT)).isFalse();
|
||||||
|
|
||||||
|
// setSavedInstanceState() as false
|
||||||
|
mViewModel.isWaitingActivityResult().set(true);
|
||||||
|
mViewModel.onSaveInstanceState(bundle);
|
||||||
|
assertThat(bundle.getBoolean(SAVED_STATE_IS_WAITING_ACTIVITY_RESULT)).isTrue();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY;
|
||||||
|
|
||||||
|
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP;
|
||||||
|
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_FIRST_RUN;
|
||||||
|
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_PORTAL_SETUP;
|
||||||
|
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_SETUP_FLOW;
|
||||||
|
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW;
|
||||||
|
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_THEME;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
|
||||||
|
|
||||||
|
public class EnrollmentRequestUtil {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static EnrollmentRequest newAllFalseRequest(@NonNull Context context) {
|
||||||
|
return newRequest(context, false, false, false, false, false, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static EnrollmentRequest newIsSuwRequest(@NonNull Context context) {
|
||||||
|
return newRequest(context, true, false, false, false, false, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static EnrollmentRequest newIsSuwDeferredRequest(@NonNull Context context) {
|
||||||
|
return newRequest(context, true, true, false, false, false, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static EnrollmentRequest newIsSuwPortalRequest(@NonNull Context context) {
|
||||||
|
return newRequest(context, true, false, true, false, false, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static EnrollmentRequest newIsSuwSuggestedActionFlowRequest(
|
||||||
|
@NonNull Context context) {
|
||||||
|
return newRequest(context, true, false, false, true, false, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static EnrollmentRequest newRequest(@NonNull Context context, boolean isSuw,
|
||||||
|
boolean isSuwDeferred, boolean isSuwPortal, boolean isSuwSuggestedActionFlow,
|
||||||
|
boolean isSuwFirstRun, boolean isFromSettingsSummery, String theme) {
|
||||||
|
Intent i = new Intent();
|
||||||
|
i.putExtra(EXTRA_IS_SETUP_FLOW, isSuw);
|
||||||
|
i.putExtra(EXTRA_IS_DEFERRED_SETUP, isSuwDeferred);
|
||||||
|
i.putExtra(EXTRA_IS_PORTAL_SETUP, isSuwPortal);
|
||||||
|
i.putExtra(EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW, isSuwSuggestedActionFlow);
|
||||||
|
i.putExtra(EXTRA_IS_FIRST_RUN, isSuwFirstRun);
|
||||||
|
i.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, isFromSettingsSummery);
|
||||||
|
if (!TextUtils.isEmpty(theme)) {
|
||||||
|
i.putExtra(EXTRA_THEME, theme);
|
||||||
|
}
|
||||||
|
return new EnrollmentRequest(i, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
@@ -25,6 +25,7 @@ import com.android.settings.accounts.AccountFeatureProvider;
|
|||||||
import com.android.settings.applications.ApplicationFeatureProvider;
|
import com.android.settings.applications.ApplicationFeatureProvider;
|
||||||
import com.android.settings.aware.AwareFeatureProvider;
|
import com.android.settings.aware.AwareFeatureProvider;
|
||||||
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
||||||
|
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
|
||||||
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
||||||
import com.android.settings.dashboard.DashboardFeatureProvider;
|
import com.android.settings.dashboard.DashboardFeatureProvider;
|
||||||
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
|
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
|
||||||
@@ -73,6 +74,7 @@ public class FakeFeatureFactory extends FeatureFactory {
|
|||||||
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
|
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
|
||||||
public final AwareFeatureProvider mAwareFeatureProvider;
|
public final AwareFeatureProvider mAwareFeatureProvider;
|
||||||
public final FaceFeatureProvider mFaceFeatureProvider;
|
public final FaceFeatureProvider mFaceFeatureProvider;
|
||||||
|
public final BiometricsRepositoryProvider mBiometricsRepositoryProvider;
|
||||||
|
|
||||||
public PanelFeatureProvider panelFeatureProvider;
|
public PanelFeatureProvider panelFeatureProvider;
|
||||||
public SlicesFeatureProvider slicesFeatureProvider;
|
public SlicesFeatureProvider slicesFeatureProvider;
|
||||||
@@ -120,6 +122,7 @@ public class FakeFeatureFactory extends FeatureFactory {
|
|||||||
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
|
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
|
||||||
mAwareFeatureProvider = mock(AwareFeatureProvider.class);
|
mAwareFeatureProvider = mock(AwareFeatureProvider.class);
|
||||||
mFaceFeatureProvider = mock(FaceFeatureProvider.class);
|
mFaceFeatureProvider = mock(FaceFeatureProvider.class);
|
||||||
|
mBiometricsRepositoryProvider = mock(BiometricsRepositoryProvider.class);
|
||||||
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
|
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
|
||||||
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
|
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
|
||||||
mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
|
mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
|
||||||
@@ -242,6 +245,11 @@ public class FakeFeatureFactory extends FeatureFactory {
|
|||||||
return mFaceFeatureProvider;
|
return mFaceFeatureProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BiometricsRepositoryProvider getBiometricsRepositoryProvider() {
|
||||||
|
return mBiometricsRepositoryProvider;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WifiTrackerLibProvider getWifiTrackerLibProvider() {
|
public WifiTrackerLibProvider getWifiTrackerLibProvider() {
|
||||||
return wifiTrackerLibProvider;
|
return wifiTrackerLibProvider;
|
||||||
|
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 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.testutils;
|
||||||
|
|
||||||
|
import androidx.arch.core.executor.ArchTaskExecutor;
|
||||||
|
import androidx.arch.core.executor.TaskExecutor;
|
||||||
|
|
||||||
|
import org.junit.rules.TestWatcher;
|
||||||
|
import org.junit.runner.Description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A JUnit Test Rule that swaps the background executor used by the Architecture Components with a
|
||||||
|
* different one which executes each task synchronously.
|
||||||
|
*
|
||||||
|
* We can't refer it in prebuilt androidX library.
|
||||||
|
* Copied it from androidx/arch/core/executor/testing/InstantTaskExecutorRule.java
|
||||||
|
*/
|
||||||
|
public class InstantTaskExecutorRule extends TestWatcher {
|
||||||
|
@Override
|
||||||
|
protected void starting(Description description) {
|
||||||
|
super.starting(description);
|
||||||
|
ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
|
||||||
|
@Override
|
||||||
|
public void executeOnDiskIO(Runnable runnable) {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postToMainThread(Runnable runnable) {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMainThread() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finished(Description description) {
|
||||||
|
super.finished(description);
|
||||||
|
ArchTaskExecutor.getInstance().setDelegate(null);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user