Merge "Fingerprint Introduction FragmentActivity"
This commit is contained in:
committed by
Android (Google) Code Review
commit
148774d287
@@ -2403,6 +2403,11 @@
|
||||
</intent-filter>
|
||||
</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"
|
||||
android:exported="false"
|
||||
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.FingerprintEnrollIntroduction;
|
||||
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.homepage.DeepLinkHomepageActivity;
|
||||
import com.android.settings.homepage.DeepLinkHomepageActivityInternal;
|
||||
@@ -225,6 +226,7 @@ public class ActivityEmbeddingRulesController {
|
||||
.buildSearchIntent(mContext, SettingsEnums.SETTINGS_HOMEPAGE);
|
||||
addActivityFilter(activityFilters, searchIntent);
|
||||
}
|
||||
addActivityFilter(activityFilters, FingerprintEnrollmentActivity.class);
|
||||
addActivityFilter(activityFilters, FingerprintEnrollIntroduction.class);
|
||||
addActivityFilter(activityFilters, FingerprintEnrollIntroductionInternal.class);
|
||||
addActivityFilter(activityFilters, FingerprintEnrollEnrolling.class);
|
||||
|
@@ -25,6 +25,7 @@ import android.content.IntentSender;
|
||||
import android.hardware.biometrics.SensorProperties;
|
||||
import android.hardware.face.FaceManager;
|
||||
import android.hardware.face.FaceSensorPropertiesInternal;
|
||||
import android.os.Bundle;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.util.Log;
|
||||
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 activityIntent The intent that started the caller's activity
|
||||
|
@@ -32,7 +32,7 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu
|
||||
/**
|
||||
* 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";
|
||||
|
||||
|
@@ -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.aware.AwareFeatureProvider;
|
||||
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
||||
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
|
||||
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
||||
import com.android.settings.dashboard.DashboardFeatureProvider;
|
||||
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
|
||||
@@ -161,6 +162,11 @@ public abstract class FeatureFactory {
|
||||
|
||||
public abstract FaceFeatureProvider getFaceFeatureProvider();
|
||||
|
||||
/**
|
||||
* Gets implementation for Biometrics repository provider.
|
||||
*/
|
||||
public abstract BiometricsRepositoryProvider getBiometricsRepositoryProvider();
|
||||
|
||||
/**
|
||||
* 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.biometrics.face.FaceFeatureProvider;
|
||||
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.BluetoothFeatureProviderImpl;
|
||||
import com.android.settings.connecteddevice.dock.DockUpdaterFeatureProviderImpl;
|
||||
@@ -104,6 +106,7 @@ public class FeatureFactoryImpl extends FeatureFactory {
|
||||
private BluetoothFeatureProvider mBluetoothFeatureProvider;
|
||||
private AwareFeatureProvider mAwareFeatureProvider;
|
||||
private FaceFeatureProvider mFaceFeatureProvider;
|
||||
private BiometricsRepositoryProvider mBiometricsRepositoryProvider;
|
||||
private WifiTrackerLibProvider mWifiTrackerLibProvider;
|
||||
private SecuritySettingsFeatureProvider mSecuritySettingsFeatureProvider;
|
||||
private AccessibilitySearchFeatureProvider mAccessibilitySearchFeatureProvider;
|
||||
@@ -305,6 +308,14 @@ public class FeatureFactoryImpl extends FeatureFactory {
|
||||
return mFaceFeatureProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiometricsRepositoryProvider getBiometricsRepositoryProvider() {
|
||||
if (mBiometricsRepositoryProvider == null) {
|
||||
mBiometricsRepositoryProvider = new BiometricsRepositoryProviderImpl();
|
||||
}
|
||||
return mBiometricsRepositoryProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WifiTrackerLibProvider getWifiTrackerLibProvider() {
|
||||
if (mWifiTrackerLibProvider == null) {
|
||||
|
@@ -27,6 +27,7 @@ import com.android.settings.accounts.AccountFeatureProvider;
|
||||
import com.android.settings.applications.ApplicationFeatureProvider;
|
||||
import com.android.settings.aware.AwareFeatureProvider;
|
||||
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
||||
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
|
||||
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
||||
import com.android.settings.dashboard.DashboardFeatureProvider;
|
||||
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
|
||||
@@ -78,6 +79,7 @@ public class FakeFeatureFactory extends FeatureFactory {
|
||||
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
|
||||
public final AwareFeatureProvider mAwareFeatureProvider;
|
||||
public final FaceFeatureProvider mFaceFeatureProvider;
|
||||
public final BiometricsRepositoryProvider mBiometricsRepositoryProvider;
|
||||
|
||||
public PanelFeatureProvider panelFeatureProvider;
|
||||
public SlicesFeatureProvider slicesFeatureProvider;
|
||||
@@ -134,6 +136,7 @@ public class FakeFeatureFactory extends FeatureFactory {
|
||||
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
|
||||
mAwareFeatureProvider = mock(AwareFeatureProvider.class);
|
||||
mFaceFeatureProvider = mock(FaceFeatureProvider.class);
|
||||
mBiometricsRepositoryProvider = mock(BiometricsRepositoryProvider.class);
|
||||
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
|
||||
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
|
||||
mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
|
||||
@@ -256,6 +259,11 @@ public class FakeFeatureFactory extends FeatureFactory {
|
||||
return mFaceFeatureProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiometricsRepositoryProvider getBiometricsRepositoryProvider() {
|
||||
return mBiometricsRepositoryProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WifiTrackerLibProvider getWifiTrackerLibProvider() {
|
||||
return wifiTrackerLibProvider;
|
||||
|
@@ -23,6 +23,7 @@ import com.android.settings.accounts.AccountFeatureProvider
|
||||
import com.android.settings.applications.ApplicationFeatureProvider
|
||||
import com.android.settings.aware.AwareFeatureProvider
|
||||
import com.android.settings.biometrics.face.FaceFeatureProvider
|
||||
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider
|
||||
import com.android.settings.bluetooth.BluetoothFeatureProvider
|
||||
import com.android.settings.dashboard.DashboardFeatureProvider
|
||||
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider
|
||||
@@ -153,6 +154,10 @@ class FakeFeatureFactory : FeatureFactory() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getBiometricsRepositoryProvider(): BiometricsRepositoryProvider {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getWifiTrackerLibProvider(): WifiTrackerLibProvider {
|
||||
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.aware.AwareFeatureProvider;
|
||||
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
||||
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
|
||||
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
||||
import com.android.settings.dashboard.DashboardFeatureProvider;
|
||||
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
|
||||
@@ -73,6 +74,7 @@ public class FakeFeatureFactory extends FeatureFactory {
|
||||
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
|
||||
public final AwareFeatureProvider mAwareFeatureProvider;
|
||||
public final FaceFeatureProvider mFaceFeatureProvider;
|
||||
public final BiometricsRepositoryProvider mBiometricsRepositoryProvider;
|
||||
|
||||
public PanelFeatureProvider panelFeatureProvider;
|
||||
public SlicesFeatureProvider slicesFeatureProvider;
|
||||
@@ -120,6 +122,7 @@ public class FakeFeatureFactory extends FeatureFactory {
|
||||
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
|
||||
mAwareFeatureProvider = mock(AwareFeatureProvider.class);
|
||||
mFaceFeatureProvider = mock(FaceFeatureProvider.class);
|
||||
mBiometricsRepositoryProvider = mock(BiometricsRepositoryProvider.class);
|
||||
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
|
||||
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
|
||||
mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
|
||||
@@ -242,6 +245,11 @@ public class FakeFeatureFactory extends FeatureFactory {
|
||||
return mFaceFeatureProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiometricsRepositoryProvider getBiometricsRepositoryProvider() {
|
||||
return mBiometricsRepositoryProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WifiTrackerLibProvider getWifiTrackerLibProvider() {
|
||||
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