Remove package biometric2 from AOSP settings
Flag: EXEMPT Remove legacy code from AOSP Bug: 351977012 Test: Build pass Change-Id: I2285ab8cc2776071c329d7432849aeffc28ed8ee
This commit is contained in:
@@ -2800,20 +2800,6 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".biometrics2.ui.view.FingerprintEnrollmentActivity"
|
|
||||||
android:exported="true"
|
|
||||||
android:theme="@style/GlifTheme.Light" />
|
|
||||||
|
|
||||||
<activity android:name=".biometrics2.ui.view.FingerprintEnrollmentActivity$InternalActivity"
|
|
||||||
android:exported="false"
|
|
||||||
android:theme="@style/GlifTheme.Light"
|
|
||||||
android:taskAffinity="com.android.settings.root" />
|
|
||||||
|
|
||||||
<activity android:name=".biometrics2.ui.view.FingerprintEnrollmentActivity$SetupActivity"
|
|
||||||
android:exported="true"
|
|
||||||
android:permission="android.permission.MANAGE_FINGERPRINT"
|
|
||||||
android:theme="@style/GlifTheme.Light" />
|
|
||||||
|
|
||||||
<activity android:name=".biometrics.fingerprint.FingerprintEnrollIntroductionInternal"
|
<activity android:name=".biometrics.fingerprint.FingerprintEnrollIntroductionInternal"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/GlifTheme.Light"
|
android:theme="@style/GlifTheme.Light"
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import com.android.settings.biometrics.face.FaceEnrollIntroductionInternal;
|
|||||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
|
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
|
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal;
|
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal;
|
||||||
import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity;
|
|
||||||
import com.android.settings.core.FeatureFlags;
|
import com.android.settings.core.FeatureFlags;
|
||||||
import com.android.settings.homepage.DeepLinkHomepageActivity;
|
import com.android.settings.homepage.DeepLinkHomepageActivity;
|
||||||
import com.android.settings.homepage.DeepLinkHomepageActivityInternal;
|
import com.android.settings.homepage.DeepLinkHomepageActivityInternal;
|
||||||
@@ -255,8 +254,6 @@ public class ActivityEmbeddingRulesController {
|
|||||||
.buildSearchIntent(mContext, SettingsEnums.SETTINGS_HOMEPAGE);
|
.buildSearchIntent(mContext, SettingsEnums.SETTINGS_HOMEPAGE);
|
||||||
addActivityFilter(activityFilters, searchIntent);
|
addActivityFilter(activityFilters, searchIntent);
|
||||||
}
|
}
|
||||||
addActivityFilter(activityFilters, FingerprintEnrollmentActivity.class);
|
|
||||||
addActivityFilter(activityFilters, FingerprintEnrollmentActivity.InternalActivity.class);
|
|
||||||
addActivityFilter(activityFilters, FingerprintEnrollIntroduction.class);
|
addActivityFilter(activityFilters, FingerprintEnrollIntroduction.class);
|
||||||
addActivityFilter(activityFilters, FingerprintEnrollIntroductionInternal.class);
|
addActivityFilter(activityFilters, FingerprintEnrollIntroductionInternal.class);
|
||||||
addActivityFilter(activityFilters, FingerprintEnrollEnrolling.class);
|
addActivityFilter(activityFilters, FingerprintEnrollEnrolling.class);
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package com.android.settings.biometrics;
|
package com.android.settings.biometrics;
|
||||||
|
|
||||||
import static android.util.FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT;
|
|
||||||
|
|
||||||
import android.annotation.IntDef;
|
import android.annotation.IntDef;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -33,7 +32,6 @@ import android.os.Bundle;
|
|||||||
import android.os.storage.StorageManager;
|
import android.os.storage.StorageManager;
|
||||||
import android.text.BidiFormatter;
|
import android.text.BidiFormatter;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.util.FeatureFlagUtils;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
@@ -50,7 +48,6 @@ import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor;
|
|||||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
|
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
|
||||||
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor;
|
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor;
|
||||||
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction;
|
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction;
|
||||||
import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity;
|
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
import com.android.settings.password.ChooseLockGeneric;
|
import com.android.settings.password.ChooseLockGeneric;
|
||||||
import com.android.settings.password.ChooseLockSettingsHelper;
|
import com.android.settings.password.ChooseLockSettingsHelper;
|
||||||
@@ -254,17 +251,8 @@ public class BiometricUtils {
|
|||||||
public static Intent getFingerprintFindSensorIntent(@NonNull Context context,
|
public static Intent getFingerprintFindSensorIntent(@NonNull Context context,
|
||||||
@NonNull Intent activityIntent) {
|
@NonNull Intent activityIntent) {
|
||||||
final boolean isSuw = WizardManagerHelper.isAnySetupWizard(activityIntent);
|
final boolean isSuw = WizardManagerHelper.isAnySetupWizard(activityIntent);
|
||||||
final Intent intent;
|
final Intent intent = new Intent(context, isSuw
|
||||||
if (FeatureFlagUtils.isEnabled(context, SETTINGS_BIOMETRICS2_ENROLLMENT)) {
|
? SetupFingerprintEnrollFindSensor.class : FingerprintEnrollFindSensor.class);
|
||||||
intent = new Intent(context, isSuw
|
|
||||||
? FingerprintEnrollmentActivity.SetupActivity.class
|
|
||||||
: FingerprintEnrollmentActivity.class);
|
|
||||||
intent.putExtra(BiometricEnrollActivity.EXTRA_SKIP_INTRO, true);
|
|
||||||
} else {
|
|
||||||
intent = new Intent(context, isSuw
|
|
||||||
? SetupFingerprintEnrollFindSensor.class
|
|
||||||
: FingerprintEnrollFindSensor.class);
|
|
||||||
}
|
|
||||||
if (isSuw) {
|
if (isSuw) {
|
||||||
SetupWizardUtils.copySetupExtras(activityIntent, intent);
|
SetupWizardUtils.copySetupExtras(activityIntent, intent);
|
||||||
}
|
}
|
||||||
@@ -279,16 +267,8 @@ public class BiometricUtils {
|
|||||||
public static Intent getFingerprintIntroIntent(@NonNull Context context,
|
public static Intent getFingerprintIntroIntent(@NonNull Context context,
|
||||||
@NonNull Intent activityIntent) {
|
@NonNull Intent activityIntent) {
|
||||||
final boolean isSuw = WizardManagerHelper.isAnySetupWizard(activityIntent);
|
final boolean isSuw = WizardManagerHelper.isAnySetupWizard(activityIntent);
|
||||||
final Intent intent;
|
final Intent intent = new Intent(context, isSuw
|
||||||
if (FeatureFlagUtils.isEnabled(context, SETTINGS_BIOMETRICS2_ENROLLMENT)) {
|
? SetupFingerprintEnrollIntroduction.class : FingerprintEnrollIntroduction.class);
|
||||||
intent = new Intent(context, isSuw
|
|
||||||
? FingerprintEnrollmentActivity.SetupActivity.class
|
|
||||||
: FingerprintEnrollmentActivity.class);
|
|
||||||
} else {
|
|
||||||
intent = new Intent(context, isSuw
|
|
||||||
? SetupFingerprintEnrollIntroduction.class
|
|
||||||
: FingerprintEnrollIntroduction.class);
|
|
||||||
}
|
|
||||||
if (isSuw) {
|
if (isSuw) {
|
||||||
WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
|
WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import android.os.UserManager;
|
|||||||
import android.text.InputFilter;
|
import android.text.InputFilter;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.FeatureFlagUtils;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImeAwareEditText;
|
import android.widget.ImeAwareEditText;
|
||||||
@@ -69,8 +68,6 @@ import com.android.settings.Utils;
|
|||||||
import com.android.settings.biometrics.BiometricEnrollBase;
|
import com.android.settings.biometrics.BiometricEnrollBase;
|
||||||
import com.android.settings.biometrics.BiometricUtils;
|
import com.android.settings.biometrics.BiometricUtils;
|
||||||
import com.android.settings.biometrics.GatekeeperPasswordProvider;
|
import com.android.settings.biometrics.GatekeeperPasswordProvider;
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
|
|
||||||
import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity;
|
|
||||||
import com.android.settings.core.SettingsBaseActivity;
|
import com.android.settings.core.SettingsBaseActivity;
|
||||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||||
import com.android.settings.dashboard.DashboardFragment;
|
import com.android.settings.dashboard.DashboardFragment;
|
||||||
@@ -801,15 +798,8 @@ public class FingerprintSettings extends SubSettings {
|
|||||||
if (KEY_FINGERPRINT_ADD.equals(key)) {
|
if (KEY_FINGERPRINT_ADD.equals(key)) {
|
||||||
mIsEnrolling = true;
|
mIsEnrolling = true;
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
if (FeatureFlagUtils.isEnabled(getContext(),
|
intent.setClassName(SETTINGS_PACKAGE_NAME,
|
||||||
FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT)) {
|
FingerprintEnrollEnrolling.class.getName());
|
||||||
intent.setClassName(SETTINGS_PACKAGE_NAME,
|
|
||||||
FingerprintEnrollmentActivity.InternalActivity.class.getName());
|
|
||||||
intent.putExtra(EnrollmentRequest.EXTRA_SKIP_FIND_SENSOR, true);
|
|
||||||
} else {
|
|
||||||
intent.setClassName(SETTINGS_PACKAGE_NAME,
|
|
||||||
FingerprintEnrollEnrolling.class.getName());
|
|
||||||
}
|
|
||||||
intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
|
intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
|
||||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
|
||||||
if (mCalibrator != null) {
|
if (mCalibrator != null) {
|
||||||
@@ -1087,12 +1077,7 @@ public class FingerprintSettings extends SubSettings {
|
|||||||
private void addFirstFingerprint(@Nullable Long gkPwHandle) {
|
private void addFirstFingerprint(@Nullable Long gkPwHandle) {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.setClassName(SETTINGS_PACKAGE_NAME,
|
intent.setClassName(SETTINGS_PACKAGE_NAME,
|
||||||
FeatureFlagUtils.isEnabled(getActivity(),
|
FingerprintEnrollIntroductionInternal.class.getName());
|
||||||
FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT)
|
|
||||||
? FingerprintEnrollmentActivity.InternalActivity.class.getName()
|
|
||||||
: FingerprintEnrollIntroductionInternal.class.getName()
|
|
||||||
);
|
|
||||||
|
|
||||||
intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true);
|
intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true);
|
||||||
intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
|
intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
|
||||||
SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE);
|
SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE);
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
# The Android Biometric team should approve all changes to biometrics2 subdirectories.
|
|
||||||
set noparent
|
|
||||||
|
|
||||||
include /src/com/android/settings/biometrics/OWNERS
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.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 android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
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 {
|
|
||||||
|
|
||||||
private static final String TAG = "FingerprintRepository";
|
|
||||||
@NonNull
|
|
||||||
private final FingerprintManager mFingerprintManager;
|
|
||||||
|
|
||||||
private List<FingerprintSensorPropertiesInternal> mSensorPropertiesCache;
|
|
||||||
|
|
||||||
public FingerprintRepository(@NonNull FingerprintManager fingerprintManager) {
|
|
||||||
mFingerprintManager = fingerprintManager;
|
|
||||||
mFingerprintManager.addAuthenticatorsRegisteredCallback(
|
|
||||||
new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
|
|
||||||
@Override
|
|
||||||
public void onAllAuthenticatorsRegistered(
|
|
||||||
List<FingerprintSensorPropertiesInternal> sensors) {
|
|
||||||
mSensorPropertiesCache = sensors;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The first sensor type is UDFPS sensor or not
|
|
||||||
*/
|
|
||||||
public boolean canAssumeUdfps() {
|
|
||||||
FingerprintSensorPropertiesInternal prop = getFirstFingerprintSensorPropertiesInternal();
|
|
||||||
return prop != null && prop.isAnyUdfpsType();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The first sensor type is Side fps sensor or not
|
|
||||||
*/
|
|
||||||
public boolean canAssumeSfps() {
|
|
||||||
FingerprintSensorPropertiesInternal prop = getFirstFingerprintSensorPropertiesInternal();
|
|
||||||
return prop != null && prop.isAnySidefpsType();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get max possible number of fingerprints for a user
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the first FingerprintSensorPropertiesInternal from FingerprintManager
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public FingerprintSensorPropertiesInternal getFirstFingerprintSensorPropertiesInternal() {
|
|
||||||
final List<FingerprintSensorPropertiesInternal> props = mSensorPropertiesCache;
|
|
||||||
if (props == null) {
|
|
||||||
// Handle this case if it really happens
|
|
||||||
Log.e(TAG, "Sensor properties cache is null");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get fingerprint enroll stage threshold
|
|
||||||
*/
|
|
||||||
public float getEnrollStageThreshold(int index) {
|
|
||||||
return mFingerprintManager.getEnrollStageThreshold(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get fingerprint enroll stage count
|
|
||||||
*/
|
|
||||||
public int getEnrollStageCount() {
|
|
||||||
return mFingerprintManager.getEnrollStageCount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.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);
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.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 {
|
|
||||||
|
|
||||||
private static volatile FingerprintRepository sFingerprintRepository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get FingerprintRepository
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public FingerprintRepository getFingerprintRepository(@NonNull Application application) {
|
|
||||||
final FingerprintManager fingerprintManager =
|
|
||||||
Utils.getFingerprintManagerOrNull(application);
|
|
||||||
if (fingerprintManager == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (sFingerprintRepository == null) {
|
|
||||||
synchronized (FingerprintRepository.class) {
|
|
||||||
if (sFingerprintRepository == null) {
|
|
||||||
sFingerprintRepository = new FingerprintRepository(fingerprintManager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sFingerprintRepository;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.factory;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
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.biometrics.fingerprint.FingerprintUpdater;
|
|
||||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
|
||||||
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.ChallengeGenerator;
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel;
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel;
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel;
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel;
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishViewModel;
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel;
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel;
|
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
|
||||||
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View model factory for biometric enrollment fragment
|
|
||||||
*/
|
|
||||||
public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
|
||||||
|
|
||||||
private static final String TAG = "BiometricsViewModelFactory";
|
|
||||||
|
|
||||||
public static final CreationExtras.Key<ChallengeGenerator> CHALLENGE_GENERATOR_KEY =
|
|
||||||
new CreationExtras.Key<ChallengeGenerator>() {};
|
|
||||||
public static final CreationExtras.Key<EnrollmentRequest> ENROLLMENT_REQUEST_KEY =
|
|
||||||
new CreationExtras.Key<EnrollmentRequest>() {};
|
|
||||||
public static final CreationExtras.Key<CredentialModel> CREDENTIAL_MODEL_KEY =
|
|
||||||
new CreationExtras.Key<CredentialModel>() {};
|
|
||||||
|
|
||||||
@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.getFeatureFactory();
|
|
||||||
final BiometricsRepositoryProvider provider =
|
|
||||||
featureFactory.getBiometricsRepositoryProvider();
|
|
||||||
|
|
||||||
if (modelClass.isAssignableFrom(AutoCredentialViewModel.class)) {
|
|
||||||
final LockPatternUtils lockPatternUtils =
|
|
||||||
featureFactory.getSecurityFeatureProvider().getLockPatternUtils(application);
|
|
||||||
final ChallengeGenerator challengeGenerator = extras.get(CHALLENGE_GENERATOR_KEY);
|
|
||||||
final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
|
|
||||||
if (challengeGenerator != null && credentialModel != null) {
|
|
||||||
return (T) new AutoCredentialViewModel(application, lockPatternUtils,
|
|
||||||
challengeGenerator, credentialModel);
|
|
||||||
}
|
|
||||||
} else if (modelClass.isAssignableFrom(DeviceFoldedViewModel.class)) {
|
|
||||||
return (T) new DeviceFoldedViewModel(new ScreenSizeFoldProvider(application),
|
|
||||||
application.getMainExecutor());
|
|
||||||
} else if (modelClass.isAssignableFrom(DeviceRotationViewModel.class)) {
|
|
||||||
return (T) new DeviceRotationViewModel(application);
|
|
||||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollFindSensorViewModel.class)) {
|
|
||||||
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
|
|
||||||
if (request != null) {
|
|
||||||
return (T) new FingerprintEnrollFindSensorViewModel(application, request.isSuw());
|
|
||||||
}
|
|
||||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollIntroViewModel.class)) {
|
|
||||||
final FingerprintRepository repository = provider.getFingerprintRepository(application);
|
|
||||||
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
|
|
||||||
final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
|
|
||||||
if (repository != null && request != null && credentialModel != null) {
|
|
||||||
return (T) new FingerprintEnrollIntroViewModel(application, repository, request,
|
|
||||||
credentialModel.getUserId());
|
|
||||||
}
|
|
||||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollmentViewModel.class)) {
|
|
||||||
final FingerprintRepository repository = provider.getFingerprintRepository(application);
|
|
||||||
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
|
|
||||||
if (repository != null && request != null) {
|
|
||||||
return (T) new FingerprintEnrollmentViewModel(application, repository, request);
|
|
||||||
}
|
|
||||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollProgressViewModel.class)) {
|
|
||||||
final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
|
|
||||||
if (credentialModel != null) {
|
|
||||||
return (T) new FingerprintEnrollProgressViewModel(application,
|
|
||||||
new FingerprintUpdater(application), credentialModel.getUserId());
|
|
||||||
}
|
|
||||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollEnrollingViewModel.class)) {
|
|
||||||
final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
|
|
||||||
final FingerprintRepository fingerprint = provider.getFingerprintRepository(
|
|
||||||
application);
|
|
||||||
if (fingerprint != null && credentialModel != null) {
|
|
||||||
return (T) new FingerprintEnrollEnrollingViewModel(application,
|
|
||||||
credentialModel.getUserId(), fingerprint);
|
|
||||||
}
|
|
||||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollFinishViewModel.class)) {
|
|
||||||
final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
|
|
||||||
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
|
|
||||||
final FingerprintRepository fingerprint = provider.getFingerprintRepository(
|
|
||||||
application);
|
|
||||||
if (fingerprint != null && credentialModel != null && request != null) {
|
|
||||||
return (T) new FingerprintEnrollFinishViewModel(application,
|
|
||||||
credentialModel.getUserId(), request, fingerprint);
|
|
||||||
}
|
|
||||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollErrorDialogViewModel.class)) {
|
|
||||||
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
|
|
||||||
if (request != null) {
|
|
||||||
return (T) new FingerprintEnrollErrorDialogViewModel(application, request.isSuw());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return create(modelClass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.model
|
|
||||||
|
|
||||||
import android.content.Intent.EXTRA_USER_ID
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.UserHandle
|
|
||||||
import androidx.annotation.VisibleForTesting
|
|
||||||
import com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE
|
|
||||||
import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN
|
|
||||||
import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
|
|
||||||
import java.time.Clock
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Secret credential data including
|
|
||||||
* 1. userId
|
|
||||||
* 2. challenge
|
|
||||||
* 3. token
|
|
||||||
* 4. gkPwHandle
|
|
||||||
*/
|
|
||||||
class CredentialModel(bundle: Bundle?, private val clock: Clock) {
|
|
||||||
|
|
||||||
private val mInitMillis = clock.millis()
|
|
||||||
|
|
||||||
/** userId for this credential */
|
|
||||||
val userId: Int = (bundle ?: Bundle()).getInt(EXTRA_USER_ID, UserHandle.myUserId())
|
|
||||||
|
|
||||||
private var clearGkPwHandleMillis: Long? = null
|
|
||||||
|
|
||||||
/** Gatekeeper password handle */
|
|
||||||
var gkPwHandle: Long = (bundle ?: Bundle()).getLong(EXTRA_KEY_GK_PW_HANDLE, INVALID_GK_PW_HANDLE)
|
|
||||||
private set
|
|
||||||
|
|
||||||
val isValidGkPwHandle: Boolean
|
|
||||||
get() = gkPwHandle != INVALID_GK_PW_HANDLE
|
|
||||||
|
|
||||||
/** Clear gatekeeper password handle data */
|
|
||||||
fun clearGkPwHandle() {
|
|
||||||
clearGkPwHandleMillis = clock.millis()
|
|
||||||
gkPwHandle = INVALID_GK_PW_HANDLE
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Check user id is valid or not */
|
|
||||||
val isValidUserId: Boolean
|
|
||||||
get() = userId != UserHandle.USER_NULL
|
|
||||||
|
|
||||||
private var updateChallengeMillis: Long? = null
|
|
||||||
|
|
||||||
var challenge: Long = (bundle ?: Bundle()).getLong(EXTRA_KEY_CHALLENGE, INVALID_CHALLENGE)
|
|
||||||
set(value) {
|
|
||||||
updateChallengeMillis = clock.millis()
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
|
|
||||||
val isValidChallenge: Boolean
|
|
||||||
get() = challenge != INVALID_CHALLENGE
|
|
||||||
|
|
||||||
private var updateTokenMillis: Long? = null
|
|
||||||
|
|
||||||
/** Challenge token */
|
|
||||||
var token: ByteArray? = (bundle ?: Bundle()).getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)
|
|
||||||
set(value) {
|
|
||||||
updateTokenMillis = clock.millis()
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
|
|
||||||
val isValidToken: Boolean
|
|
||||||
get() = token != null
|
|
||||||
|
|
||||||
/** Returns a string representation of the object */
|
|
||||||
override fun toString(): String {
|
|
||||||
val gkPwHandleLen = "$gkPwHandle".length
|
|
||||||
val tokenLen = token?.size ?: 0
|
|
||||||
val challengeLen = "$challenge".length
|
|
||||||
return (javaClass.simpleName + ":{initMillis:$mInitMillis"
|
|
||||||
+ ", userId:$userId"
|
|
||||||
+ ", challenge:{len:$challengeLen"
|
|
||||||
+ ", updateMillis:$updateChallengeMillis}"
|
|
||||||
+ ", token:{len:$tokenLen, isValid:$isValidToken"
|
|
||||||
+ ", updateMillis:$updateTokenMillis}"
|
|
||||||
+ ", gkPwHandle:{len:$gkPwHandleLen, isValid:$isValidGkPwHandle"
|
|
||||||
+ ", clearMillis:$clearGkPwHandleMillis}"
|
|
||||||
+ " }")
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/** Default value for an invalid challenge */
|
|
||||||
@VisibleForTesting
|
|
||||||
const val INVALID_CHALLENGE = -1L
|
|
||||||
|
|
||||||
/** Default value if GkPwHandle is invalid */
|
|
||||||
@VisibleForTesting
|
|
||||||
const val INVALID_GK_PW_HANDLE = 0L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.model
|
|
||||||
|
|
||||||
/** Biometric Enrollment progress */
|
|
||||||
class EnrollmentProgress(val steps: Int, val remaining: Int) {
|
|
||||||
|
|
||||||
val isInitialStep: Boolean
|
|
||||||
get() = steps == INITIAL_STEPS
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return ("${javaClass.simpleName}@${Integer.toHexString(hashCode())}"
|
|
||||||
+ "{steps:$steps, remaining:$remaining}")
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val INITIAL_STEPS = -1
|
|
||||||
const val INITIAL_REMAINING = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.model
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import com.android.settings.SetupWizardUtils
|
|
||||||
import com.android.settings.biometrics.BiometricEnrollActivity.EXTRA_SKIP_INTRO
|
|
||||||
import com.google.android.setupcompat.util.WizardManagerHelper
|
|
||||||
import com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Biometric enrollment generic intent data, which includes
|
|
||||||
* 1. isSuw
|
|
||||||
* 2. isAfterSuwOrSuwSuggestedAction
|
|
||||||
* 3. theme
|
|
||||||
* 4. isFromSettingsSummery
|
|
||||||
* 5. isSkipIntro
|
|
||||||
* 6. isSkipFindSensor
|
|
||||||
* 7. a helper method, getSetupWizardExtras
|
|
||||||
*/
|
|
||||||
class EnrollmentRequest(
|
|
||||||
intent: Intent,
|
|
||||||
context: Context,
|
|
||||||
isSetupActivity: Boolean
|
|
||||||
) {
|
|
||||||
val isSuw: Boolean = isSetupActivity && WizardManagerHelper.isAnySetupWizard(intent)
|
|
||||||
|
|
||||||
val isAfterSuwOrSuwSuggestedAction = (isSetupActivity
|
|
||||||
&& (WizardManagerHelper.isDeferredSetupWizard(intent)
|
|
||||||
|| WizardManagerHelper.isPortalSetupWizard(intent)
|
|
||||||
|| intent.getBooleanExtra(EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW, false)))
|
|
||||||
|
|
||||||
private val _suwExtras = getSuwExtras(isSuw, intent)
|
|
||||||
|
|
||||||
val isSkipIntro = intent.getBooleanExtra(EXTRA_SKIP_INTRO, false)
|
|
||||||
|
|
||||||
val isSkipFindSensor = intent.getBooleanExtra(EXTRA_SKIP_FIND_SENSOR, false)
|
|
||||||
|
|
||||||
val theme = SetupWizardUtils.getTheme(context, intent)
|
|
||||||
|
|
||||||
val suwExtras: Bundle
|
|
||||||
get() = Bundle(_suwExtras)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of the object
|
|
||||||
*/
|
|
||||||
override fun toString(): String {
|
|
||||||
return (javaClass.simpleName + ":{isSuw:" + isSuw
|
|
||||||
+ ", isAfterSuwOrSuwSuggestedAction:" + isAfterSuwOrSuwSuggestedAction
|
|
||||||
+ "}")
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val EXTRA_SKIP_FIND_SENSOR = "skip_find_sensor"
|
|
||||||
private fun getSuwExtras(isSuw: Boolean, intent: Intent): Bundle {
|
|
||||||
val toIntent = Intent()
|
|
||||||
if (isSuw) {
|
|
||||||
SetupWizardUtils.copySetupExtras(intent, toIntent)
|
|
||||||
}
|
|
||||||
return toIntent.extras ?: Bundle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.model
|
|
||||||
|
|
||||||
/** Enrolling status message (help or error) */
|
|
||||||
class EnrollmentStatusMessage(val msgId: Int, string: CharSequence?) {
|
|
||||||
|
|
||||||
/** Status string */
|
|
||||||
val str: CharSequence = string ?: ""
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "${javaClass.simpleName}@${Integer.toHexString(hashCode())}{id:$msgId, str:$str}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.model
|
|
||||||
|
|
||||||
|
|
||||||
enum class FingerprintEnrollable {
|
|
||||||
// Unconfirmed case, this value is invalid, and view shall bypass this value
|
|
||||||
FINGERPRINT_ENROLLABLE_UNKNOWN,
|
|
||||||
// User is allowed to enrolled a new fingerprint
|
|
||||||
FINGERPRINT_ENROLLABLE_OK,
|
|
||||||
// User is not allowed to enroll because the number has reached maximum
|
|
||||||
FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
class FingerprintEnrollIntroStatus(
|
|
||||||
private val mHasScrollToBottom: Boolean,
|
|
||||||
/** Enrollable status. It means that user is allowed to enroll a new fingerprint or not. */
|
|
||||||
val enrollableStatus: FingerprintEnrollable
|
|
||||||
) {
|
|
||||||
/** Get info for this onboarding introduction page has scrolled to bottom or not */
|
|
||||||
fun hasScrollToBottom(): Boolean {
|
|
||||||
return mHasScrollToBottom
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return ("${javaClass.simpleName}@${Integer.toHexString(hashCode())}"
|
|
||||||
+ "{scrollToBottom:$mHasScrollToBottom"
|
|
||||||
+ ", enrollableStatus:$enrollableStatus}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.view
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import com.android.settings.R
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Icon Touch dialog
|
|
||||||
*/
|
|
||||||
class FingerprintEnrollEnrollingIconTouchDialog : DialogFragment() {
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
|
|
||||||
requireActivity().bindFingerprintEnrollEnrollingIconTouchDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.bindFingerprintEnrollEnrollingIconTouchDialog(): AlertDialog =
|
|
||||||
AlertDialog.Builder(this, R.style.Theme_AlertDialog)
|
|
||||||
.setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title)
|
|
||||||
.setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message)
|
|
||||||
.setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok) {
|
|
||||||
dialog: DialogInterface?, _: Int -> dialog?.dismiss()
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
@@ -1,513 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.view
|
|
||||||
|
|
||||||
import android.animation.Animator
|
|
||||||
import android.animation.ObjectAnimator
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.PorterDuff
|
|
||||||
import android.graphics.drawable.Animatable2
|
|
||||||
import android.graphics.drawable.AnimatedVectorDrawable
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.graphics.drawable.LayerDrawable
|
|
||||||
import android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.TextUtils
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.animation.AnimationUtils.loadInterpolator
|
|
||||||
import android.view.animation.Interpolator
|
|
||||||
import android.widget.ProgressBar
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.activity.OnBackPressedCallback
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import com.android.settings.R
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentProgress
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
|
|
||||||
import com.google.android.setupcompat.template.FooterBarMixin
|
|
||||||
import com.google.android.setupcompat.template.FooterButton
|
|
||||||
import com.google.android.setupdesign.GlifLayout
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragment is used to handle enrolling process for rfps
|
|
||||||
*/
|
|
||||||
class FingerprintEnrollEnrollingRfpsFragment : Fragment() {
|
|
||||||
|
|
||||||
private var _enrollingViewModel: FingerprintEnrollEnrollingViewModel? = null
|
|
||||||
private val enrollingViewModel: FingerprintEnrollEnrollingViewModel
|
|
||||||
get() = _enrollingViewModel!!
|
|
||||||
|
|
||||||
private var _progressViewModel: FingerprintEnrollProgressViewModel? = null
|
|
||||||
private val progressViewModel: FingerprintEnrollProgressViewModel
|
|
||||||
get() = _progressViewModel!!
|
|
||||||
|
|
||||||
private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null
|
|
||||||
private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel
|
|
||||||
get() = _errorDialogViewModel!!
|
|
||||||
|
|
||||||
private var fastOutSlowInInterpolator: Interpolator? = null
|
|
||||||
private var linearOutSlowInInterpolator: Interpolator? = null
|
|
||||||
private var fastOutLinearInInterpolator: Interpolator? = null
|
|
||||||
|
|
||||||
private var isAnimationCancelled = false
|
|
||||||
|
|
||||||
private var enrollingView: GlifLayout? = null
|
|
||||||
private val progressBar: ProgressBar
|
|
||||||
get() = enrollingView!!.findViewById(R.id.fingerprint_progress_bar)!!
|
|
||||||
|
|
||||||
private var progressAnim: ObjectAnimator? = null
|
|
||||||
|
|
||||||
private val errorText: TextView
|
|
||||||
get() = enrollingView!!.findViewById(R.id.error_text)!!
|
|
||||||
|
|
||||||
private val iconAnimationDrawable: AnimatedVectorDrawable?
|
|
||||||
get() = (progressBar.background as LayerDrawable)
|
|
||||||
.findDrawableByLayerId(R.id.fingerprint_animation) as AnimatedVectorDrawable?
|
|
||||||
|
|
||||||
private val iconBackgroundBlinksDrawable: AnimatedVectorDrawable?
|
|
||||||
get() = (progressBar.background as LayerDrawable)
|
|
||||||
.findDrawableByLayerId(R.id.fingerprint_background) as AnimatedVectorDrawable?
|
|
||||||
|
|
||||||
private var iconTouchCount = 0
|
|
||||||
|
|
||||||
private val touchAgainRunnable = Runnable {
|
|
||||||
showError(
|
|
||||||
// Use enrollingView to getString to prevent activity is missing during rotation
|
|
||||||
enrollingView!!.context.getString(
|
|
||||||
R.string.security_settings_fingerprint_enroll_lift_touch_again
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val onSkipClickListener = View.OnClickListener { _: View? ->
|
|
||||||
enrollingViewModel.setOnSkipPressed()
|
|
||||||
cancelEnrollment(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var enrollingCancelSignal: Any? = null
|
|
||||||
|
|
||||||
private val progressObserver = Observer { progress: EnrollmentProgress? ->
|
|
||||||
if (progress != null && progress.steps >= 0) {
|
|
||||||
onEnrollmentProgressChange(progress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val helpMessageObserver = Observer { helpMessage: EnrollmentStatusMessage? ->
|
|
||||||
helpMessage?.let { onEnrollmentHelp(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val errorMessageObserver = Observer { errorMessage: EnrollmentStatusMessage? ->
|
|
||||||
Log.d(TAG, "errorMessageObserver($errorMessage)")
|
|
||||||
errorMessage?.let { onEnrollmentError(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val canceledSignalObserver = Observer { canceledSignal: Any? ->
|
|
||||||
canceledSignal?.let { onEnrollmentCanceled(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val onBackPressedCallback: OnBackPressedCallback =
|
|
||||||
object : OnBackPressedCallback(true) {
|
|
||||||
override fun handleOnBackPressed() {
|
|
||||||
isEnabled = false
|
|
||||||
enrollingViewModel.setOnBackPressed()
|
|
||||||
cancelEnrollment(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
|
||||||
ViewModelProvider(requireActivity()).let { provider ->
|
|
||||||
_enrollingViewModel = provider[FingerprintEnrollEnrollingViewModel::class.java]
|
|
||||||
_progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
|
|
||||||
_errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java]
|
|
||||||
}
|
|
||||||
super.onAttach(context)
|
|
||||||
requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetach() {
|
|
||||||
onBackPressedCallback.isEnabled = false
|
|
||||||
super.onDetach()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
enrollingView = inflater.inflate(
|
|
||||||
R.layout.fingerprint_enroll_enrolling, container, false
|
|
||||||
) as GlifLayout
|
|
||||||
return enrollingView!!
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
iconAnimationDrawable!!.registerAnimationCallback(iconAnimationCallback)
|
|
||||||
|
|
||||||
progressBar.setOnTouchListener { _: View?, event: MotionEvent ->
|
|
||||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
|
||||||
iconTouchCount++
|
|
||||||
if (iconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) {
|
|
||||||
showIconTouchDialog()
|
|
||||||
} else {
|
|
||||||
progressBar.postDelayed(
|
|
||||||
showDialogRunnable,
|
|
||||||
ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (event.actionMasked == MotionEvent.ACTION_CANCEL
|
|
||||||
|| event.actionMasked == MotionEvent.ACTION_UP
|
|
||||||
) {
|
|
||||||
progressBar.removeCallbacks(showDialogRunnable)
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
requireActivity().bindFingerprintEnrollEnrollingRfpsView(
|
|
||||||
view = enrollingView!!,
|
|
||||||
onSkipClickListener = onSkipClickListener
|
|
||||||
)
|
|
||||||
|
|
||||||
fastOutSlowInInterpolator =
|
|
||||||
loadInterpolator(requireContext(), android.R.interpolator.fast_out_slow_in)
|
|
||||||
linearOutSlowInInterpolator =
|
|
||||||
loadInterpolator(requireContext(), android.R.interpolator.linear_out_slow_in)
|
|
||||||
fastOutLinearInInterpolator =
|
|
||||||
loadInterpolator(requireContext(), android.R.interpolator.fast_out_linear_in)
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
errorDialogViewModel.triggerRetryFlow.collect { retryEnrollment() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun retryEnrollment() {
|
|
||||||
isAnimationCancelled = false
|
|
||||||
startIconAnimation()
|
|
||||||
startEnrollment()
|
|
||||||
|
|
||||||
clearError()
|
|
||||||
updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
|
|
||||||
updateTitleAndDescription()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
|
|
||||||
val isEnrolling = progressViewModel.isEnrolling
|
|
||||||
val isErrorDialogShown = errorDialogViewModel.isDialogShown
|
|
||||||
Log.d(TAG, "onStart(), isEnrolling:$isEnrolling, isErrorDialog:$isErrorDialogShown")
|
|
||||||
if (!isErrorDialogShown) {
|
|
||||||
isAnimationCancelled = false
|
|
||||||
startIconAnimation()
|
|
||||||
startEnrollment()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
|
|
||||||
updateTitleAndDescription()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startIconAnimation() {
|
|
||||||
iconAnimationDrawable?.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stopIconAnimation() {
|
|
||||||
isAnimationCancelled = true
|
|
||||||
iconAnimationDrawable?.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
stopIconAnimation()
|
|
||||||
removeEnrollmentObservers()
|
|
||||||
val isEnrolling = progressViewModel.isEnrolling
|
|
||||||
val isConfigChange = requireActivity().isChangingConfigurations
|
|
||||||
Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange")
|
|
||||||
if (isEnrolling && !isConfigChange) {
|
|
||||||
cancelEnrollment(false)
|
|
||||||
}
|
|
||||||
super.onStop()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeEnrollmentObservers() {
|
|
||||||
progressViewModel.errorMessageLiveData.removeObserver(errorMessageObserver)
|
|
||||||
progressViewModel.progressLiveData.removeObserver(progressObserver)
|
|
||||||
progressViewModel.helpMessageLiveData.removeObserver(helpMessageObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) {
|
|
||||||
if (!progressViewModel.isEnrolling) {
|
|
||||||
Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
removeEnrollmentObservers()
|
|
||||||
if (waitForLastCancelErrMsg) {
|
|
||||||
progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver)
|
|
||||||
} else {
|
|
||||||
enrollingCancelSignal = null
|
|
||||||
}
|
|
||||||
val cancelResult: Boolean = progressViewModel.cancelEnrollment()
|
|
||||||
if (!cancelResult) {
|
|
||||||
Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startEnrollment() {
|
|
||||||
enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_ENROLL)
|
|
||||||
if (enrollingCancelSignal == null) {
|
|
||||||
Log.e(TAG, "startEnrollment(), failed")
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "startEnrollment(), success")
|
|
||||||
}
|
|
||||||
progressViewModel.progressLiveData.observe(this, progressObserver)
|
|
||||||
progressViewModel.helpMessageLiveData.observe(this, helpMessageObserver)
|
|
||||||
progressViewModel.errorMessageLiveData.observe(this, errorMessageObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentHelp(helpMessage: EnrollmentStatusMessage) {
|
|
||||||
Log.d(TAG, "onEnrollmentHelp($helpMessage)")
|
|
||||||
val helpStr: CharSequence = helpMessage.str
|
|
||||||
if (!TextUtils.isEmpty(helpStr)) {
|
|
||||||
errorText.removeCallbacks(touchAgainRunnable)
|
|
||||||
showError(helpStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) {
|
|
||||||
stopIconAnimation()
|
|
||||||
|
|
||||||
cancelEnrollment(true)
|
|
||||||
lifecycleScope.launch {
|
|
||||||
Log.d(TAG, "newDialog $errorMessage")
|
|
||||||
errorDialogViewModel.newDialog(errorMessage.msgId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentCanceled(canceledSignal: Any) {
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal"
|
|
||||||
)
|
|
||||||
if (enrollingCancelSignal === canceledSignal) {
|
|
||||||
progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver)
|
|
||||||
progressViewModel.clearProgressLiveData()
|
|
||||||
if (enrollingViewModel.onBackPressed) {
|
|
||||||
enrollingViewModel.onCancelledDueToOnBackPressed()
|
|
||||||
} else if (enrollingViewModel.onSkipPressed) {
|
|
||||||
enrollingViewModel.onCancelledDueToOnSkipPressed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentProgressChange(progress: EnrollmentProgress) {
|
|
||||||
updateProgress(true /* animate */, progress)
|
|
||||||
updateTitleAndDescription()
|
|
||||||
animateFlash()
|
|
||||||
errorText.removeCallbacks(touchAgainRunnable)
|
|
||||||
errorText.postDelayed(touchAgainRunnable, HINT_TIMEOUT_DURATION.toLong())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateProgress(animate: Boolean, enrollmentProgress: EnrollmentProgress) {
|
|
||||||
val progress = getProgress(enrollmentProgress)
|
|
||||||
Log.d(TAG, "updateProgress($animate, $enrollmentProgress), old:${progressBar.progress}"
|
|
||||||
+ ", new:$progress")
|
|
||||||
|
|
||||||
// Only clear the error when progress has been made.
|
|
||||||
// TODO (b/234772728) Add tests.
|
|
||||||
if (progressBar.progress < progress) {
|
|
||||||
clearError()
|
|
||||||
}
|
|
||||||
if (animate) {
|
|
||||||
animateProgress(progress)
|
|
||||||
} else {
|
|
||||||
progressBar.progress = progress
|
|
||||||
if (progress >= PROGRESS_BAR_MAX) {
|
|
||||||
delayedFinishRunnable.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getProgress(progress: EnrollmentProgress): Int {
|
|
||||||
if (progress.steps == -1) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
val displayProgress = 0.coerceAtLeast(progress.steps + 1 - progress.remaining)
|
|
||||||
return PROGRESS_BAR_MAX * displayProgress / (progress.steps + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showError(error: CharSequence) {
|
|
||||||
errorText.text = error
|
|
||||||
if (errorText.visibility == View.INVISIBLE) {
|
|
||||||
errorText.visibility = View.VISIBLE
|
|
||||||
errorText.translationY = enrollingView!!.context.resources.getDimensionPixelSize(
|
|
||||||
R.dimen.fingerprint_error_text_appear_distance
|
|
||||||
).toFloat()
|
|
||||||
errorText.alpha = 0f
|
|
||||||
errorText.animate()
|
|
||||||
.alpha(1f)
|
|
||||||
.translationY(0f)
|
|
||||||
.setDuration(200)
|
|
||||||
.setInterpolator(linearOutSlowInInterpolator)
|
|
||||||
.start()
|
|
||||||
} else {
|
|
||||||
errorText.animate().cancel()
|
|
||||||
errorText.alpha = 1f
|
|
||||||
errorText.translationY = 0f
|
|
||||||
}
|
|
||||||
if (isResumed && enrollingViewModel.isAccessibilityEnabled) {
|
|
||||||
enrollingViewModel.vibrateError(javaClass.simpleName + "::showError")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearError() {
|
|
||||||
if (errorText.visibility == View.VISIBLE) {
|
|
||||||
errorText.animate()
|
|
||||||
.alpha(0f)
|
|
||||||
.translationY(
|
|
||||||
resources.getDimensionPixelSize(
|
|
||||||
R.dimen.fingerprint_error_text_disappear_distance
|
|
||||||
).toFloat()
|
|
||||||
)
|
|
||||||
.setDuration(100)
|
|
||||||
.setInterpolator(fastOutLinearInInterpolator)
|
|
||||||
.withEndAction { errorText.visibility = View.INVISIBLE }
|
|
||||||
.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun animateProgress(progress: Int) {
|
|
||||||
progressAnim?.cancel()
|
|
||||||
val anim = ObjectAnimator.ofInt(
|
|
||||||
progressBar /* target */,
|
|
||||||
"progress" /* propertyName */,
|
|
||||||
progressBar.progress /* values[0] */,
|
|
||||||
progress /* values[1] */
|
|
||||||
)
|
|
||||||
anim.addListener(progressAnimationListener)
|
|
||||||
anim.interpolator = fastOutSlowInInterpolator
|
|
||||||
anim.setDuration(ANIMATION_DURATION)
|
|
||||||
anim.start()
|
|
||||||
progressAnim = anim
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun animateFlash() {
|
|
||||||
iconBackgroundBlinksDrawable?.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateTitleAndDescription() {
|
|
||||||
val progressLiveData: EnrollmentProgress = progressViewModel.progressLiveData.value!!
|
|
||||||
GlifLayoutHelper(activity!!, enrollingView!!).setDescriptionText(
|
|
||||||
enrollingView!!.context.getString(
|
|
||||||
if (progressLiveData.steps == -1)
|
|
||||||
R.string.security_settings_fingerprint_enroll_start_message
|
|
||||||
else
|
|
||||||
R.string.security_settings_fingerprint_enroll_repeat_message
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showIconTouchDialog() {
|
|
||||||
iconTouchCount = 0
|
|
||||||
enrollingViewModel.showIconTouchDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val showDialogRunnable = Runnable { showIconTouchDialog() }
|
|
||||||
|
|
||||||
private val progressAnimationListener: Animator.AnimatorListener =
|
|
||||||
object : Animator.AnimatorListener {
|
|
||||||
override fun onAnimationStart(animation: Animator) {
|
|
||||||
startIconAnimation()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAnimationRepeat(animation: Animator) {}
|
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
|
||||||
stopIconAnimation()
|
|
||||||
if (progressBar.progress >= PROGRESS_BAR_MAX) {
|
|
||||||
progressBar.postDelayed(delayedFinishRunnable, ANIMATION_DURATION)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAnimationCancel(animation: Animator) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give the user a chance to see progress completed before jumping to the next stage.
|
|
||||||
private val delayedFinishRunnable = Runnable { enrollingViewModel.onEnrollingDone() }
|
|
||||||
|
|
||||||
private val iconAnimationCallback: Animatable2.AnimationCallback =
|
|
||||||
object : Animatable2.AnimationCallback() {
|
|
||||||
override fun onAnimationEnd(d: Drawable) {
|
|
||||||
if (isAnimationCancelled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start animation after it has ended.
|
|
||||||
progressBar.post { startIconAnimation() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val DEBUG = false
|
|
||||||
private const val TAG = "FingerprintEnrollEnrollingRfpsFragment"
|
|
||||||
private const val PROGRESS_BAR_MAX = 10000
|
|
||||||
private const val ANIMATION_DURATION = 250L
|
|
||||||
private const val ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN: Long = 500
|
|
||||||
private const val ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If we don't see progress during this time, we show an error message to remind the users that
|
|
||||||
* they need to lift the finger and touch again.
|
|
||||||
*/
|
|
||||||
private const val HINT_TIMEOUT_DURATION = 2500
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun FragmentActivity.bindFingerprintEnrollEnrollingRfpsView(
|
|
||||||
view: GlifLayout,
|
|
||||||
onSkipClickListener: View.OnClickListener
|
|
||||||
) {
|
|
||||||
GlifLayoutHelper(this, view).let {
|
|
||||||
it.setDescriptionText(
|
|
||||||
getString(
|
|
||||||
R.string.security_settings_fingerprint_enroll_start_message
|
|
||||||
)
|
|
||||||
)
|
|
||||||
it.setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title)
|
|
||||||
}
|
|
||||||
|
|
||||||
view.findViewById<ProgressBar>(R.id.fingerprint_progress_bar)!!
|
|
||||||
.progressBackgroundTintMode = PorterDuff.Mode.SRC
|
|
||||||
|
|
||||||
view.getMixin(FooterBarMixin::class.java).secondaryButton =
|
|
||||||
FooterButton.Builder(this)
|
|
||||||
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
|
||||||
.setListener(onSkipClickListener)
|
|
||||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
|
||||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
@@ -1,669 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.view
|
|
||||||
|
|
||||||
import android.animation.Animator
|
|
||||||
import android.animation.ObjectAnimator
|
|
||||||
import android.annotation.RawRes
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.graphics.PorterDuff
|
|
||||||
import android.graphics.PorterDuffColorFilter
|
|
||||||
import android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator
|
|
||||||
import android.view.animation.AnimationUtils
|
|
||||||
import android.view.animation.Interpolator
|
|
||||||
import android.widget.ProgressBar
|
|
||||||
import android.widget.RelativeLayout
|
|
||||||
import androidx.activity.OnBackPressedCallback
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import com.airbnb.lottie.LottieAnimationView
|
|
||||||
import com.airbnb.lottie.LottieComposition
|
|
||||||
import com.airbnb.lottie.LottieCompositionFactory
|
|
||||||
import com.airbnb.lottie.LottieProperty
|
|
||||||
import com.airbnb.lottie.model.KeyPath
|
|
||||||
import com.android.settings.R
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentProgress
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
|
|
||||||
import com.google.android.setupcompat.template.FooterBarMixin
|
|
||||||
import com.google.android.setupcompat.template.FooterButton
|
|
||||||
import com.google.android.setupdesign.GlifLayout
|
|
||||||
import com.google.android.setupdesign.template.DescriptionMixin
|
|
||||||
import com.google.android.setupdesign.template.HeaderMixin
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragment is used to handle enrolling process for sfps
|
|
||||||
*/
|
|
||||||
class FingerprintEnrollEnrollingSfpsFragment : Fragment() {
|
|
||||||
|
|
||||||
private var _enrollingViewModel: FingerprintEnrollEnrollingViewModel? = null
|
|
||||||
private val enrollingViewModel: FingerprintEnrollEnrollingViewModel
|
|
||||||
get() = _enrollingViewModel!!
|
|
||||||
|
|
||||||
private var _progressViewModel: FingerprintEnrollProgressViewModel? = null
|
|
||||||
private val progressViewModel: FingerprintEnrollProgressViewModel
|
|
||||||
get() = _progressViewModel!!
|
|
||||||
|
|
||||||
private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null
|
|
||||||
private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel
|
|
||||||
get() = _errorDialogViewModel!!
|
|
||||||
|
|
||||||
private val fastOutSlowInInterpolator: Interpolator
|
|
||||||
get() = AnimationUtils.loadInterpolator(
|
|
||||||
activity,
|
|
||||||
androidx.appcompat.R.interpolator.fast_out_slow_in,
|
|
||||||
)
|
|
||||||
|
|
||||||
private var enrollingView: GlifLayout? = null
|
|
||||||
|
|
||||||
private val progressBar: ProgressBar
|
|
||||||
get() = enrollingView!!.findViewById(R.id.fingerprint_progress_bar)!!
|
|
||||||
|
|
||||||
private var progressAnim: ObjectAnimator? = null
|
|
||||||
|
|
||||||
private val progressAnimationListener: Animator.AnimatorListener =
|
|
||||||
object : Animator.AnimatorListener {
|
|
||||||
override fun onAnimationStart(animation: Animator) {}
|
|
||||||
override fun onAnimationRepeat(animation: Animator) {}
|
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
|
||||||
if (progressBar.progress >= PROGRESS_BAR_MAX) {
|
|
||||||
progressBar.postDelayed(delayedFinishRunnable, PROGRESS_ANIMATION_DURATION)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAnimationCancel(animation: Animator) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val illustrationLottie: LottieAnimationView
|
|
||||||
get() = enrollingView!!.findViewById(R.id.illustration_lottie)!!
|
|
||||||
|
|
||||||
private var haveShownSfpsNoAnimationLottie = false
|
|
||||||
private var haveShownSfpsCenterLottie = false
|
|
||||||
private var haveShownSfpsTipLottie = false
|
|
||||||
private var haveShownSfpsLeftEdgeLottie = false
|
|
||||||
private var haveShownSfpsRightEdgeLottie = false
|
|
||||||
|
|
||||||
private var helpAnimation: ObjectAnimator? = null
|
|
||||||
|
|
||||||
private var iconTouchCount = 0
|
|
||||||
|
|
||||||
private val showIconTouchDialogRunnable = Runnable { showIconTouchDialog() }
|
|
||||||
|
|
||||||
private var enrollingCancelSignal: Any? = null
|
|
||||||
|
|
||||||
// Give the user a chance to see progress completed before jumping to the next stage.
|
|
||||||
private val delayedFinishRunnable = Runnable { enrollingViewModel.onEnrollingDone() }
|
|
||||||
|
|
||||||
private val onSkipClickListener = View.OnClickListener { _: View? ->
|
|
||||||
enrollingViewModel.setOnSkipPressed()
|
|
||||||
cancelEnrollment(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val progressObserver = Observer { progress: EnrollmentProgress? ->
|
|
||||||
if (progress != null && progress.steps >= 0) {
|
|
||||||
onEnrollmentProgressChange(progress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val helpMessageObserver = Observer { helpMessage: EnrollmentStatusMessage? ->
|
|
||||||
helpMessage?.let { onEnrollmentHelp(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val errorMessageObserver = Observer { errorMessage: EnrollmentStatusMessage? ->
|
|
||||||
Log.d(TAG, "errorMessageObserver($errorMessage)")
|
|
||||||
errorMessage?.let { onEnrollmentError(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val canceledSignalObserver = Observer { canceledSignal: Any? ->
|
|
||||||
Log.d(TAG, "canceledSignalObserver($canceledSignal)")
|
|
||||||
canceledSignal?.let { onEnrollmentCanceled(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val onBackPressedCallback: OnBackPressedCallback =
|
|
||||||
object : OnBackPressedCallback(true) {
|
|
||||||
override fun handleOnBackPressed() {
|
|
||||||
isEnabled = false
|
|
||||||
enrollingViewModel.setOnBackPressed()
|
|
||||||
cancelEnrollment(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
|
||||||
ViewModelProvider(requireActivity()).let { provider ->
|
|
||||||
_enrollingViewModel = provider[FingerprintEnrollEnrollingViewModel::class.java]
|
|
||||||
_progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
|
|
||||||
_errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java]
|
|
||||||
}
|
|
||||||
super.onAttach(context)
|
|
||||||
requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetach() {
|
|
||||||
onBackPressedCallback.isEnabled = false
|
|
||||||
super.onDetach()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
enrollingView = inflater.inflate(
|
|
||||||
R.layout.sfps_enroll_enrolling,
|
|
||||||
container, false
|
|
||||||
) as GlifLayout
|
|
||||||
return enrollingView
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
requireActivity().bindFingerprintEnrollEnrollingSfpsView(
|
|
||||||
view = enrollingView!!,
|
|
||||||
onSkipClickListener = onSkipClickListener
|
|
||||||
)
|
|
||||||
|
|
||||||
// setHelpAnimation()
|
|
||||||
helpAnimation = ObjectAnimator.ofFloat(
|
|
||||||
enrollingView!!.findViewById<RelativeLayout>(R.id.progress_lottie)!!,
|
|
||||||
"translationX" /* propertyName */,
|
|
||||||
0f,
|
|
||||||
HELP_ANIMATION_TRANSLATION_X,
|
|
||||||
-1 * HELP_ANIMATION_TRANSLATION_X,
|
|
||||||
HELP_ANIMATION_TRANSLATION_X,
|
|
||||||
0f
|
|
||||||
).also {
|
|
||||||
it.interpolator = AccelerateDecelerateInterpolator()
|
|
||||||
it.setDuration(HELP_ANIMATION_DURATION)
|
|
||||||
it.setAutoCancel(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
progressBar.setOnTouchListener { _: View?, event: MotionEvent ->
|
|
||||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
|
||||||
iconTouchCount++
|
|
||||||
if (iconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) {
|
|
||||||
showIconTouchDialog()
|
|
||||||
} else {
|
|
||||||
progressBar.postDelayed(
|
|
||||||
showIconTouchDialogRunnable,
|
|
||||||
ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (event.actionMasked == MotionEvent.ACTION_CANCEL
|
|
||||||
|| event.actionMasked == MotionEvent.ACTION_UP
|
|
||||||
) {
|
|
||||||
progressBar.removeCallbacks(showIconTouchDialogRunnable)
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
errorDialogViewModel.triggerRetryFlow.collect { retryEnrollment() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun retryEnrollment() {
|
|
||||||
startEnrollment()
|
|
||||||
updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
val isEnrolling = progressViewModel.isEnrolling
|
|
||||||
val isErrorDialogShown = errorDialogViewModel.isDialogShown
|
|
||||||
Log.d(TAG, "onStart(), isEnrolling:$isEnrolling, isErrorDialog:$isErrorDialogShown")
|
|
||||||
if (!isErrorDialogShown) {
|
|
||||||
startEnrollment()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
|
|
||||||
progressViewModel.helpMessageLiveData.value.let {
|
|
||||||
if (it != null) {
|
|
||||||
onEnrollmentHelp(it)
|
|
||||||
} else {
|
|
||||||
clearError()
|
|
||||||
updateTitleAndDescription()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
removeEnrollmentObservers()
|
|
||||||
val isEnrolling = progressViewModel.isEnrolling
|
|
||||||
val isConfigChange = requireActivity().isChangingConfigurations
|
|
||||||
Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange")
|
|
||||||
if (isEnrolling && !isConfigChange) {
|
|
||||||
cancelEnrollment(false)
|
|
||||||
}
|
|
||||||
super.onStop()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeEnrollmentObservers() {
|
|
||||||
progressViewModel.errorMessageLiveData.removeObserver(errorMessageObserver)
|
|
||||||
progressViewModel.progressLiveData.removeObserver(progressObserver)
|
|
||||||
progressViewModel.helpMessageLiveData.removeObserver(helpMessageObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) {
|
|
||||||
if (!progressViewModel.isEnrolling) {
|
|
||||||
Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
removeEnrollmentObservers()
|
|
||||||
if (waitForLastCancelErrMsg) {
|
|
||||||
progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver)
|
|
||||||
} else {
|
|
||||||
enrollingCancelSignal = null
|
|
||||||
}
|
|
||||||
val cancelResult: Boolean = progressViewModel.cancelEnrollment()
|
|
||||||
if (!cancelResult) {
|
|
||||||
Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startEnrollment() {
|
|
||||||
enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_ENROLL)
|
|
||||||
if (enrollingCancelSignal == null) {
|
|
||||||
Log.e(TAG, "startEnrollment(), failed")
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "startEnrollment(), success")
|
|
||||||
}
|
|
||||||
progressViewModel.progressLiveData.observe(this, progressObserver)
|
|
||||||
progressViewModel.helpMessageLiveData.observe(this, helpMessageObserver)
|
|
||||||
progressViewModel.errorMessageLiveData.observe(this, errorMessageObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun configureEnrollmentStage(description: CharSequence, @RawRes lottie: Int) {
|
|
||||||
GlifLayoutHelper(requireActivity(), enrollingView!!).setDescriptionText(description)
|
|
||||||
LottieCompositionFactory.fromRawRes(activity, lottie)
|
|
||||||
.addListener { c: LottieComposition ->
|
|
||||||
illustrationLottie.setComposition(c)
|
|
||||||
illustrationLottie.visibility = View.VISIBLE
|
|
||||||
illustrationLottie.playAnimation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val currentSfpsStage: Int
|
|
||||||
get() {
|
|
||||||
val progressLiveData: EnrollmentProgress =
|
|
||||||
progressViewModel.progressLiveData.value
|
|
||||||
?: return STAGE_UNKNOWN
|
|
||||||
val progressSteps: Int = progressLiveData.steps - progressLiveData.remaining
|
|
||||||
return if (progressSteps < getStageThresholdSteps(0)) {
|
|
||||||
SFPS_STAGE_NO_ANIMATION
|
|
||||||
} else if (progressSteps < getStageThresholdSteps(1)) {
|
|
||||||
SFPS_STAGE_CENTER
|
|
||||||
} else if (progressSteps < getStageThresholdSteps(2)) {
|
|
||||||
SFPS_STAGE_FINGERTIP
|
|
||||||
} else if (progressSteps < getStageThresholdSteps(3)) {
|
|
||||||
SFPS_STAGE_LEFT_EDGE
|
|
||||||
} else {
|
|
||||||
SFPS_STAGE_RIGHT_EDGE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentHelp(helpMessage: EnrollmentStatusMessage) {
|
|
||||||
Log.d(TAG, "onEnrollmentHelp($helpMessage)")
|
|
||||||
val helpStr: CharSequence = helpMessage.str
|
|
||||||
if (helpStr.isNotEmpty()) {
|
|
||||||
showError(helpStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) {
|
|
||||||
cancelEnrollment(true)
|
|
||||||
lifecycleScope.launch {
|
|
||||||
Log.d(TAG, "newDialog $errorMessage")
|
|
||||||
errorDialogViewModel.newDialog(errorMessage.msgId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentCanceled(canceledSignal: Any) {
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal"
|
|
||||||
)
|
|
||||||
if (enrollingCancelSignal === canceledSignal) {
|
|
||||||
progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver)
|
|
||||||
progressViewModel.clearProgressLiveData()
|
|
||||||
if (enrollingViewModel.onBackPressed) {
|
|
||||||
enrollingViewModel.onCancelledDueToOnBackPressed()
|
|
||||||
} else if (enrollingViewModel.onSkipPressed) {
|
|
||||||
enrollingViewModel.onCancelledDueToOnSkipPressed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun announceEnrollmentProgress(announcement: CharSequence) {
|
|
||||||
enrollingViewModel.sendAccessibilityEvent(announcement)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentProgressChange(progress: EnrollmentProgress) {
|
|
||||||
updateProgress(true /* animate */, progress)
|
|
||||||
if (enrollingViewModel.isAccessibilityEnabled) {
|
|
||||||
val percent: Int =
|
|
||||||
((progress.steps - progress.remaining).toFloat() / progress.steps.toFloat() * 100).toInt()
|
|
||||||
val announcement: CharSequence = getString(
|
|
||||||
R.string.security_settings_sfps_enroll_progress_a11y_message, percent
|
|
||||||
)
|
|
||||||
announceEnrollmentProgress(announcement)
|
|
||||||
illustrationLottie.contentDescription =
|
|
||||||
getString(R.string.security_settings_sfps_animation_a11y_label, percent)
|
|
||||||
}
|
|
||||||
updateTitleAndDescription()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateProgress(animate: Boolean, enrollmentProgress: EnrollmentProgress) {
|
|
||||||
if (!progressViewModel.isEnrolling) {
|
|
||||||
Log.d(TAG, "Enrollment not started yet")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val progress = getProgress(enrollmentProgress)
|
|
||||||
Log.d(TAG, "updateProgress($animate, $enrollmentProgress), old:${progressBar.progress}"
|
|
||||||
+ ", new:$progress")
|
|
||||||
|
|
||||||
// Only clear the error when progress has been made.
|
|
||||||
// TODO (b/234772728) Add tests.
|
|
||||||
if (progressBar.progress < progress) {
|
|
||||||
clearError()
|
|
||||||
}
|
|
||||||
if (animate) {
|
|
||||||
animateProgress(progress)
|
|
||||||
} else {
|
|
||||||
progressBar.progress = progress
|
|
||||||
if (progress >= PROGRESS_BAR_MAX) {
|
|
||||||
delayedFinishRunnable.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getProgress(progress: EnrollmentProgress): Int {
|
|
||||||
if (progress.steps == -1) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
val displayProgress = 0.coerceAtLeast(progress.steps + 1 - progress.remaining)
|
|
||||||
return PROGRESS_BAR_MAX * displayProgress / (progress.steps + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showError(error: CharSequence) {
|
|
||||||
enrollingView!!.let {
|
|
||||||
it.headerText = error
|
|
||||||
it.headerTextView.contentDescription = error
|
|
||||||
GlifLayoutHelper(requireActivity(), it).setDescriptionText("")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isResumed && !helpAnimation!!.isRunning) {
|
|
||||||
helpAnimation!!.start()
|
|
||||||
}
|
|
||||||
applySfpsErrorDynamicColors(true)
|
|
||||||
if (isResumed && enrollingViewModel.isAccessibilityEnabled) {
|
|
||||||
enrollingViewModel.vibrateError(javaClass.simpleName + "::showError")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearError() {
|
|
||||||
applySfpsErrorDynamicColors(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun animateProgress(progress: Int) {
|
|
||||||
progressAnim?.cancel()
|
|
||||||
progressAnim = ObjectAnimator.ofInt(
|
|
||||||
progressBar,
|
|
||||||
"progress",
|
|
||||||
progressBar.progress,
|
|
||||||
progress
|
|
||||||
).also {
|
|
||||||
it.addListener(progressAnimationListener)
|
|
||||||
it.interpolator = fastOutSlowInInterpolator
|
|
||||||
it.setDuration(PROGRESS_ANIMATION_DURATION)
|
|
||||||
it.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies dynamic colors corresponding to showing or clearing errors on the progress bar
|
|
||||||
* and finger lottie for SFPS
|
|
||||||
*/
|
|
||||||
private fun applySfpsErrorDynamicColors(isError: Boolean) {
|
|
||||||
progressBar.applyProgressBarDynamicColor(requireContext(), isError)
|
|
||||||
illustrationLottie.applyLottieDynamicColor(requireContext(), isError)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getStageThresholdSteps(index: Int): Int {
|
|
||||||
val progressLiveData: EnrollmentProgress? =
|
|
||||||
progressViewModel.progressLiveData.value
|
|
||||||
if (progressLiveData == null || progressLiveData.steps == -1) {
|
|
||||||
Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return (progressLiveData.steps
|
|
||||||
* enrollingViewModel.getEnrollStageThreshold(index)).roundToInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateTitleAndDescription() {
|
|
||||||
val helper = GlifLayoutHelper(requireActivity(), enrollingView!!)
|
|
||||||
if (enrollingViewModel.isAccessibilityEnabled) {
|
|
||||||
enrollingViewModel.clearTalkback()
|
|
||||||
helper.glifLayout.descriptionTextView.accessibilityLiveRegion =
|
|
||||||
View.ACCESSIBILITY_LIVE_REGION_POLITE
|
|
||||||
}
|
|
||||||
val stage = currentSfpsStage
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(
|
|
||||||
TAG, "updateTitleAndDescription, stage:" + stage
|
|
||||||
+ ", noAnimation:" + haveShownSfpsNoAnimationLottie
|
|
||||||
+ ", center:" + haveShownSfpsCenterLottie
|
|
||||||
+ ", tip:" + haveShownSfpsTipLottie
|
|
||||||
+ ", leftEdge:" + haveShownSfpsLeftEdgeLottie
|
|
||||||
+ ", rightEdge:" + haveShownSfpsRightEdgeLottie
|
|
||||||
)
|
|
||||||
}
|
|
||||||
when (stage) {
|
|
||||||
SFPS_STAGE_NO_ANIMATION -> {
|
|
||||||
helper.setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title)
|
|
||||||
if (!haveShownSfpsNoAnimationLottie) {
|
|
||||||
haveShownSfpsNoAnimationLottie = true
|
|
||||||
illustrationLottie.contentDescription =
|
|
||||||
getString(R.string.security_settings_sfps_animation_a11y_label, 0)
|
|
||||||
configureEnrollmentStage(
|
|
||||||
getString(R.string.security_settings_sfps_enroll_start_message),
|
|
||||||
R.raw.sfps_lottie_no_animation
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SFPS_STAGE_CENTER -> {
|
|
||||||
helper.setHeaderText(R.string.security_settings_sfps_enroll_finger_center_title)
|
|
||||||
if (!haveShownSfpsCenterLottie) {
|
|
||||||
haveShownSfpsCenterLottie = true
|
|
||||||
configureEnrollmentStage(
|
|
||||||
getString(R.string.security_settings_sfps_enroll_start_message),
|
|
||||||
R.raw.sfps_lottie_pad_center
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SFPS_STAGE_FINGERTIP -> {
|
|
||||||
helper.setHeaderText(R.string.security_settings_sfps_enroll_fingertip_title)
|
|
||||||
if (!haveShownSfpsTipLottie) {
|
|
||||||
haveShownSfpsTipLottie = true
|
|
||||||
configureEnrollmentStage("", R.raw.sfps_lottie_tip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SFPS_STAGE_LEFT_EDGE -> {
|
|
||||||
helper.setHeaderText(R.string.security_settings_sfps_enroll_left_edge_title)
|
|
||||||
if (!haveShownSfpsLeftEdgeLottie) {
|
|
||||||
haveShownSfpsLeftEdgeLottie = true
|
|
||||||
configureEnrollmentStage("", R.raw.sfps_lottie_left_edge)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SFPS_STAGE_RIGHT_EDGE -> {
|
|
||||||
helper.setHeaderText(R.string.security_settings_sfps_enroll_right_edge_title)
|
|
||||||
if (!haveShownSfpsRightEdgeLottie) {
|
|
||||||
haveShownSfpsRightEdgeLottie = true
|
|
||||||
configureEnrollmentStage("", R.raw.sfps_lottie_right_edge)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
STAGE_UNKNOWN -> {
|
|
||||||
// Don't use BiometricEnrollBase#setHeaderText, since that invokes setTitle,
|
|
||||||
// which gets announced for a11y upon entering the page. For SFPS, we want to
|
|
||||||
// announce a different string for a11y upon entering the page.
|
|
||||||
helper.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title)
|
|
||||||
helper.setDescriptionText(
|
|
||||||
getString(R.string.security_settings_sfps_enroll_start_message)
|
|
||||||
)
|
|
||||||
val description: CharSequence = getString(
|
|
||||||
R.string.security_settings_sfps_enroll_find_sensor_message
|
|
||||||
)
|
|
||||||
helper.glifLayout.headerTextView.contentDescription = description
|
|
||||||
helper.activity.title = description
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
helper.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title)
|
|
||||||
helper.setDescriptionText(
|
|
||||||
getString(R.string.security_settings_sfps_enroll_start_message)
|
|
||||||
)
|
|
||||||
val description: CharSequence = getString(
|
|
||||||
R.string.security_settings_sfps_enroll_find_sensor_message
|
|
||||||
)
|
|
||||||
helper.glifLayout.headerTextView.contentDescription = description
|
|
||||||
helper.activity.title = description
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showIconTouchDialog() {
|
|
||||||
iconTouchCount = 0
|
|
||||||
enrollingViewModel.showIconTouchDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val TAG = FingerprintEnrollEnrollingSfpsFragment::class.java.simpleName
|
|
||||||
private const val DEBUG = false
|
|
||||||
private const val PROGRESS_BAR_MAX = 10000
|
|
||||||
private const val HELP_ANIMATION_DURATION = 550L
|
|
||||||
private const val HELP_ANIMATION_TRANSLATION_X = 40f
|
|
||||||
private const val PROGRESS_ANIMATION_DURATION = 250L
|
|
||||||
private const val ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN: Long = 500
|
|
||||||
private const val ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3
|
|
||||||
private const val STAGE_UNKNOWN = -1
|
|
||||||
private const val SFPS_STAGE_NO_ANIMATION = 0
|
|
||||||
private const val SFPS_STAGE_CENTER = 1
|
|
||||||
private const val SFPS_STAGE_FINGERTIP = 2
|
|
||||||
private const val SFPS_STAGE_LEFT_EDGE = 3
|
|
||||||
private const val SFPS_STAGE_RIGHT_EDGE = 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun FragmentActivity.bindFingerprintEnrollEnrollingSfpsView(
|
|
||||||
view: GlifLayout,
|
|
||||||
onSkipClickListener: View.OnClickListener
|
|
||||||
) {
|
|
||||||
GlifLayoutHelper(this, view).setDescriptionText(
|
|
||||||
getString(R.string.security_settings_fingerprint_enroll_start_message)
|
|
||||||
)
|
|
||||||
|
|
||||||
view.getMixin(FooterBarMixin::class.java).secondaryButton = FooterButton.Builder(this)
|
|
||||||
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
|
||||||
.setListener(onSkipClickListener)
|
|
||||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
|
||||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
view.findViewById<ProgressBar>(R.id.fingerprint_progress_bar)!!.progressBackgroundTintMode =
|
|
||||||
PorterDuff.Mode.SRC
|
|
||||||
|
|
||||||
view.findViewById<ProgressBar>(R.id.fingerprint_progress_bar)!!
|
|
||||||
.applyProgressBarDynamicColor(this, false)
|
|
||||||
|
|
||||||
view.findViewById<LottieAnimationView>(R.id.illustration_lottie)!!
|
|
||||||
.applyLottieDynamicColor(this, false)
|
|
||||||
|
|
||||||
view.maybeHideSfpsText(resources.configuration.orientation)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ProgressBar.applyProgressBarDynamicColor(context: Context, isError: Boolean) {
|
|
||||||
progressTintList = ColorStateList.valueOf(
|
|
||||||
context.getColor(
|
|
||||||
if (isError)
|
|
||||||
R.color.sfps_enrollment_progress_bar_error_color
|
|
||||||
else
|
|
||||||
R.color.sfps_enrollment_progress_bar_fill_color
|
|
||||||
)
|
|
||||||
)
|
|
||||||
progressTintMode = PorterDuff.Mode.SRC
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun LottieAnimationView.applyLottieDynamicColor(context: Context, isError: Boolean) {
|
|
||||||
addValueCallback(
|
|
||||||
KeyPath(".blue100", "**"),
|
|
||||||
LottieProperty.COLOR_FILTER
|
|
||||||
) {
|
|
||||||
PorterDuffColorFilter(
|
|
||||||
context.getColor(
|
|
||||||
if (isError)
|
|
||||||
R.color.sfps_enrollment_fp_error_color
|
|
||||||
else
|
|
||||||
R.color.sfps_enrollment_fp_captured_color
|
|
||||||
),
|
|
||||||
PorterDuff.Mode.SRC_ATOP
|
|
||||||
)
|
|
||||||
}
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun GlifLayout.maybeHideSfpsText(@Configuration.Orientation orientation: Int) {
|
|
||||||
val headerMixin: HeaderMixin = getMixin(HeaderMixin::class.java)
|
|
||||||
val descriptionMixin: DescriptionMixin = getMixin(DescriptionMixin::class.java)
|
|
||||||
|
|
||||||
val isLandscape = (orientation == Configuration.ORIENTATION_LANDSCAPE)
|
|
||||||
headerMixin.setAutoTextSizeEnabled(isLandscape)
|
|
||||||
if (isLandscape) {
|
|
||||||
headerMixin.textView.minLines = 0
|
|
||||||
headerMixin.textView.maxLines = 10
|
|
||||||
descriptionMixin.textView.minLines = 0
|
|
||||||
descriptionMixin.textView.maxLines = 10
|
|
||||||
} else {
|
|
||||||
headerMixin.textView.setLines(4)
|
|
||||||
// hide the description
|
|
||||||
descriptionMixin.textView.setLines(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,707 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.view
|
|
||||||
|
|
||||||
import android.annotation.RawRes
|
|
||||||
import android.content.Context
|
|
||||||
import android.hardware.biometrics.BiometricFingerprintConstants
|
|
||||||
import android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL
|
|
||||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.Surface
|
|
||||||
import android.view.Surface.ROTATION_270
|
|
||||||
import android.view.Surface.ROTATION_90
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.RelativeLayout
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.activity.OnBackPressedCallback
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import com.airbnb.lottie.LottieAnimationView
|
|
||||||
import com.airbnb.lottie.LottieComposition
|
|
||||||
import com.airbnb.lottie.LottieCompositionFactory
|
|
||||||
import com.android.settings.R
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentProgress
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.widget.UdfpsEnrollView
|
|
||||||
import com.android.settingslib.display.DisplayDensityUtils
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragment is used to handle enrolling process for udfps
|
|
||||||
*/
|
|
||||||
class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
|
|
||||||
|
|
||||||
private var _enrollingViewModel: FingerprintEnrollEnrollingViewModel? = null
|
|
||||||
private val enrollingViewModel: FingerprintEnrollEnrollingViewModel
|
|
||||||
get() = _enrollingViewModel!!
|
|
||||||
|
|
||||||
private var _rotationViewModel: DeviceRotationViewModel? = null
|
|
||||||
private val rotationViewModel: DeviceRotationViewModel
|
|
||||||
get() = _rotationViewModel!!
|
|
||||||
|
|
||||||
private var _progressViewModel: FingerprintEnrollProgressViewModel? = null
|
|
||||||
private val progressViewModel: FingerprintEnrollProgressViewModel
|
|
||||||
get() = _progressViewModel!!
|
|
||||||
|
|
||||||
private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null
|
|
||||||
private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel
|
|
||||||
get() = _errorDialogViewModel!!
|
|
||||||
|
|
||||||
private var illustrationLottie: LottieAnimationView? = null
|
|
||||||
|
|
||||||
private var haveShownTipLottie = false
|
|
||||||
private var haveShownLeftEdgeLottie = false
|
|
||||||
private var haveShownRightEdgeLottie = false
|
|
||||||
private var haveShownCenterLottie = false
|
|
||||||
private var haveShownGuideLottie = false
|
|
||||||
|
|
||||||
private var enrollingView: RelativeLayout? = null
|
|
||||||
|
|
||||||
private val titleText: TextView
|
|
||||||
get() = enrollingView!!.findViewById(R.id.suc_layout_title)!!
|
|
||||||
|
|
||||||
private val subTitleText: TextView
|
|
||||||
get() = enrollingView!!.findViewById(R.id.sud_layout_subtitle)!!
|
|
||||||
|
|
||||||
private val udfpsEnrollView: UdfpsEnrollView
|
|
||||||
get() = enrollingView!!.findViewById(R.id.udfps_animation_view)!!
|
|
||||||
|
|
||||||
private val skipBtn: Button
|
|
||||||
get() = enrollingView!!.findViewById(R.id.skip_btn)!!
|
|
||||||
|
|
||||||
private val icon: ImageView
|
|
||||||
get() = enrollingView!!.findViewById(R.id.sud_layout_icon)!!
|
|
||||||
|
|
||||||
private val shouldShowLottie: Boolean
|
|
||||||
get() {
|
|
||||||
val displayDensity = DisplayDensityUtils(requireContext())
|
|
||||||
val currentDensityIndex: Int = displayDensity.currentIndexForDefaultDisplay
|
|
||||||
val currentDensity: Int =
|
|
||||||
displayDensity.defaultDisplayDensityValues[currentDensityIndex]
|
|
||||||
val defaultDensity: Int = displayDensity.defaultDensityForDefaultDisplay
|
|
||||||
return defaultDensity == currentDensity
|
|
||||||
}
|
|
||||||
|
|
||||||
private val isAccessibilityEnabled
|
|
||||||
get() = enrollingViewModel.isAccessibilityEnabled
|
|
||||||
|
|
||||||
private var rotation = -1
|
|
||||||
|
|
||||||
private var enrollingCancelSignal: Any? = null
|
|
||||||
|
|
||||||
private val onSkipClickListener = View.OnClickListener { _: View? ->
|
|
||||||
enrollingViewModel.setOnSkipPressed()
|
|
||||||
cancelEnrollment(true) // TODO Add test after b/273640000 fixed
|
|
||||||
}
|
|
||||||
|
|
||||||
private val progressObserver = Observer { progress: EnrollmentProgress? ->
|
|
||||||
if (progress != null && progress.steps >= 0) {
|
|
||||||
onEnrollmentProgressChange(progress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val helpMessageObserver = Observer { helpMessage: EnrollmentStatusMessage? ->
|
|
||||||
Log.d(TAG, "helpMessageObserver($helpMessage)")
|
|
||||||
helpMessage?.let { onEnrollmentHelp(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val errorMessageObserver = Observer { errorMessage: EnrollmentStatusMessage? ->
|
|
||||||
Log.d(TAG, "errorMessageObserver($errorMessage)")
|
|
||||||
errorMessage?.let { onEnrollmentError(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val canceledSignalObserver = Observer { canceledSignal: Any? ->
|
|
||||||
Log.d(TAG, "canceledSignalObserver($canceledSignal)")
|
|
||||||
canceledSignal?.let { onEnrollmentCanceled(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val acquireObserver =
|
|
||||||
Observer { isAcquiredGood: Boolean? -> isAcquiredGood?.let { onAcquired(it) } }
|
|
||||||
|
|
||||||
private val pointerDownObserver =
|
|
||||||
Observer { sensorId: Int? -> sensorId?.let { onPointerDown(it) } }
|
|
||||||
|
|
||||||
private val pointerUpObserver =
|
|
||||||
Observer { sensorId: Int? -> sensorId?.let { onPointerUp(it) } }
|
|
||||||
|
|
||||||
private val rotationObserver =
|
|
||||||
Observer { rotation: Int? -> rotation?.let { onRotationChanged(it) } }
|
|
||||||
|
|
||||||
private val onBackPressedCallback: OnBackPressedCallback =
|
|
||||||
object : OnBackPressedCallback(true) {
|
|
||||||
override fun handleOnBackPressed() {
|
|
||||||
isEnabled = false
|
|
||||||
enrollingViewModel.setOnBackPressed()
|
|
||||||
cancelEnrollment(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give the user a chance to see progress completed before jumping to the next stage.
|
|
||||||
private val delayedFinishRunnable = Runnable { enrollingViewModel.onEnrollingDone() }
|
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
|
||||||
ViewModelProvider(requireActivity()).let { provider ->
|
|
||||||
_enrollingViewModel = provider[FingerprintEnrollEnrollingViewModel::class.java]
|
|
||||||
_rotationViewModel = provider[DeviceRotationViewModel::class.java]
|
|
||||||
_progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
|
|
||||||
_errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java]
|
|
||||||
}
|
|
||||||
super.onAttach(context)
|
|
||||||
requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetach() {
|
|
||||||
onBackPressedCallback.isEnabled = false
|
|
||||||
super.onDetach()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View = (inflater.inflate(
|
|
||||||
R.layout.udfps_enroll_enrolling_v2, container, false
|
|
||||||
) as RelativeLayout).also {
|
|
||||||
enrollingView = it
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
rotation = rotationViewModel.liveData.value!!
|
|
||||||
updateIllustrationLottie(rotation)
|
|
||||||
|
|
||||||
requireActivity().bindFingerprintEnrollEnrollingUdfpsView(
|
|
||||||
view = enrollingView!!,
|
|
||||||
sensorProperties = enrollingViewModel.firstFingerprintSensorPropertiesInternal!!,
|
|
||||||
rotation = rotation,
|
|
||||||
onSkipClickListener = onSkipClickListener,
|
|
||||||
)
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
errorDialogViewModel.triggerRetryFlow.collect { retryEnrollment() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun retryEnrollment() {
|
|
||||||
reattachUdfpsEnrollView()
|
|
||||||
|
|
||||||
startEnrollment()
|
|
||||||
|
|
||||||
updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
|
|
||||||
progressViewModel.helpMessageLiveData.value.let {
|
|
||||||
if (it != null) {
|
|
||||||
onEnrollmentHelp(it)
|
|
||||||
} else {
|
|
||||||
updateTitleAndDescription()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
val isEnrolling = progressViewModel.isEnrolling
|
|
||||||
val isErrorDialogShown = errorDialogViewModel.isDialogShown
|
|
||||||
Log.d(TAG, "onStart(), isEnrolling:$isEnrolling, isErrorDialog:$isErrorDialogShown")
|
|
||||||
if (!isErrorDialogShown) {
|
|
||||||
startEnrollment()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
|
|
||||||
progressViewModel.helpMessageLiveData.value.let {
|
|
||||||
if (it != null) {
|
|
||||||
onEnrollmentHelp(it)
|
|
||||||
} else {
|
|
||||||
updateTitleAndDescription()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reattachUdfpsEnrollView() {
|
|
||||||
enrollingView!!.let {
|
|
||||||
val newUdfpsView = LayoutInflater.from(requireActivity()).inflate(
|
|
||||||
R.layout.udfps_enroll_enrolling_v2_udfps_view,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
val index = it.indexOfChild(udfpsEnrollView)
|
|
||||||
val lp = udfpsEnrollView.layoutParams
|
|
||||||
|
|
||||||
it.removeView(udfpsEnrollView)
|
|
||||||
it.addView(newUdfpsView, index, lp)
|
|
||||||
udfpsEnrollView.setSensorProperties(
|
|
||||||
enrollingViewModel.firstFingerprintSensorPropertiesInternal
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear lottie status
|
|
||||||
haveShownTipLottie = false
|
|
||||||
haveShownLeftEdgeLottie = false
|
|
||||||
haveShownRightEdgeLottie = false
|
|
||||||
haveShownCenterLottie = false
|
|
||||||
haveShownGuideLottie = false
|
|
||||||
illustrationLottie?.let {
|
|
||||||
it.contentDescription = ""
|
|
||||||
it.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
rotationViewModel.liveData.observe(this, rotationObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
rotationViewModel.liveData.removeObserver(rotationObserver)
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
removeEnrollmentObservers()
|
|
||||||
val isEnrolling = progressViewModel.isEnrolling
|
|
||||||
val isConfigChange = requireActivity().isChangingConfigurations
|
|
||||||
Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange")
|
|
||||||
if (isEnrolling && !isConfigChange) {
|
|
||||||
cancelEnrollment(false)
|
|
||||||
}
|
|
||||||
super.onStop()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeEnrollmentObservers() {
|
|
||||||
progressViewModel.errorMessageLiveData.removeObserver(errorMessageObserver)
|
|
||||||
progressViewModel.progressLiveData.removeObserver(progressObserver)
|
|
||||||
progressViewModel.helpMessageLiveData.removeObserver(helpMessageObserver)
|
|
||||||
progressViewModel.acquireLiveData.removeObserver(acquireObserver)
|
|
||||||
progressViewModel.pointerDownLiveData.removeObserver(pointerDownObserver)
|
|
||||||
progressViewModel.pointerUpLiveData.removeObserver(pointerUpObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) {
|
|
||||||
if (!progressViewModel.isEnrolling) {
|
|
||||||
Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
removeEnrollmentObservers()
|
|
||||||
if (waitForLastCancelErrMsg) {
|
|
||||||
progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver)
|
|
||||||
} else {
|
|
||||||
enrollingCancelSignal = null
|
|
||||||
}
|
|
||||||
val cancelResult: Boolean = progressViewModel.cancelEnrollment()
|
|
||||||
if (!cancelResult) {
|
|
||||||
Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startEnrollment() {
|
|
||||||
enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_ENROLL)
|
|
||||||
if (enrollingCancelSignal == null) {
|
|
||||||
Log.e(TAG, "startEnrollment(), failed")
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "startEnrollment(), success")
|
|
||||||
}
|
|
||||||
progressViewModel.progressLiveData.observe(this, progressObserver)
|
|
||||||
progressViewModel.helpMessageLiveData.observe(this, helpMessageObserver)
|
|
||||||
progressViewModel.errorMessageLiveData.observe(this, errorMessageObserver)
|
|
||||||
progressViewModel.acquireLiveData.observe(this, acquireObserver)
|
|
||||||
progressViewModel.pointerDownLiveData.observe(this, pointerDownObserver)
|
|
||||||
progressViewModel.pointerUpLiveData.observe(this, pointerUpObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateProgress(animate: Boolean, enrollmentProgress: EnrollmentProgress) {
|
|
||||||
if (!progressViewModel.isEnrolling) {
|
|
||||||
Log.d(TAG, "Enrollment not started yet")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val progress = getProgress(enrollmentProgress)
|
|
||||||
Log.d(TAG, "updateProgress($animate, $enrollmentProgress), progress:$progress")
|
|
||||||
|
|
||||||
if (enrollmentProgress.steps != -1) {
|
|
||||||
udfpsEnrollView.onEnrollmentProgress(
|
|
||||||
enrollmentProgress.remaining,
|
|
||||||
enrollmentProgress.steps
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress >= PROGRESS_BAR_MAX) {
|
|
||||||
if (animate) {
|
|
||||||
// Wait animations to finish, then proceed to next page
|
|
||||||
activity!!.mainThreadHandler.postDelayed(delayedFinishRunnable, 400L)
|
|
||||||
} else {
|
|
||||||
delayedFinishRunnable.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getProgress(progress: EnrollmentProgress): Int {
|
|
||||||
if (progress.steps == -1) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
val displayProgress = 0.coerceAtLeast(progress.steps + 1 - progress.remaining)
|
|
||||||
return PROGRESS_BAR_MAX * displayProgress / (progress.steps + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateTitleAndDescription() {
|
|
||||||
Log.d(TAG, "updateTitleAndDescription($currentStage)")
|
|
||||||
when (currentStage) {
|
|
||||||
STAGE_CENTER -> {
|
|
||||||
titleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title)
|
|
||||||
if (isAccessibilityEnabled || illustrationLottie == null) {
|
|
||||||
subTitleText.setText(R.string.security_settings_udfps_enroll_start_message)
|
|
||||||
} else if (!haveShownCenterLottie) {
|
|
||||||
haveShownCenterLottie = true
|
|
||||||
// Note: Update string reference when differentiate in between udfps & sfps
|
|
||||||
illustrationLottie!!.contentDescription = getString(R.string.security_settings_sfps_enroll_finger_center_title)
|
|
||||||
configureEnrollmentStage(R.raw.udfps_center_hint_lottie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
STAGE_GUIDED -> {
|
|
||||||
titleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title)
|
|
||||||
if (isAccessibilityEnabled || illustrationLottie == null) {
|
|
||||||
subTitleText.setText(
|
|
||||||
R.string.security_settings_udfps_enroll_repeat_a11y_message
|
|
||||||
)
|
|
||||||
} else if (!haveShownGuideLottie) {
|
|
||||||
haveShownGuideLottie = true
|
|
||||||
illustrationLottie!!.contentDescription =
|
|
||||||
getString(R.string.security_settings_fingerprint_enroll_repeat_message)
|
|
||||||
// TODO(b/228100413) Could customize guided lottie animation
|
|
||||||
configureEnrollmentStage(R.raw.udfps_center_hint_lottie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
STAGE_FINGERTIP -> {
|
|
||||||
titleText.setText(R.string.security_settings_udfps_enroll_fingertip_title)
|
|
||||||
if (!haveShownTipLottie && illustrationLottie != null) {
|
|
||||||
haveShownTipLottie = true
|
|
||||||
illustrationLottie!!.contentDescription =
|
|
||||||
getString(R.string.security_settings_udfps_tip_fingerprint_help)
|
|
||||||
configureEnrollmentStage(R.raw.udfps_tip_hint_lottie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
STAGE_LEFT_EDGE -> {
|
|
||||||
titleText.setText(R.string.security_settings_udfps_enroll_left_edge_title)
|
|
||||||
if (!haveShownLeftEdgeLottie && illustrationLottie != null) {
|
|
||||||
haveShownLeftEdgeLottie = true
|
|
||||||
illustrationLottie!!.contentDescription =
|
|
||||||
getString(R.string.security_settings_udfps_side_fingerprint_help)
|
|
||||||
configureEnrollmentStage(R.raw.udfps_left_edge_hint_lottie)
|
|
||||||
} else if (illustrationLottie == null) {
|
|
||||||
if (isStageHalfCompleted) {
|
|
||||||
subTitleText.setText(
|
|
||||||
R.string.security_settings_fingerprint_enroll_repeat_message
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
subTitleText.setText(R.string.security_settings_udfps_enroll_edge_message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
STAGE_RIGHT_EDGE -> {
|
|
||||||
titleText.setText(R.string.security_settings_udfps_enroll_right_edge_title)
|
|
||||||
if (!haveShownRightEdgeLottie && illustrationLottie != null) {
|
|
||||||
haveShownRightEdgeLottie = true
|
|
||||||
illustrationLottie!!.contentDescription =
|
|
||||||
getString(R.string.security_settings_udfps_side_fingerprint_help)
|
|
||||||
configureEnrollmentStage(R.raw.udfps_right_edge_hint_lottie)
|
|
||||||
} else if (illustrationLottie == null) {
|
|
||||||
if (isStageHalfCompleted) {
|
|
||||||
subTitleText.setText(
|
|
||||||
R.string.security_settings_fingerprint_enroll_repeat_message
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
subTitleText.setText(R.string.security_settings_udfps_enroll_edge_message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
STAGE_UNKNOWN -> {
|
|
||||||
titleText.setText(R.string.security_settings_fingerprint_enroll_udfps_title)
|
|
||||||
subTitleText.setText(R.string.security_settings_udfps_enroll_start_message)
|
|
||||||
val description: CharSequence = getString(
|
|
||||||
R.string.security_settings_udfps_enroll_a11y
|
|
||||||
)
|
|
||||||
requireActivity().title = description
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
titleText.setText(R.string.security_settings_fingerprint_enroll_udfps_title)
|
|
||||||
subTitleText.setText(R.string.security_settings_udfps_enroll_start_message)
|
|
||||||
val description: CharSequence = getString(
|
|
||||||
R.string.security_settings_udfps_enroll_a11y
|
|
||||||
)
|
|
||||||
requireActivity().title = description
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateIllustrationLottie(@Surface.Rotation rotation: Int) {
|
|
||||||
if (rotation == ROTATION_90 || rotation == ROTATION_270) {
|
|
||||||
illustrationLottie = null
|
|
||||||
} else if (shouldShowLottie) {
|
|
||||||
illustrationLottie =
|
|
||||||
enrollingView!!.findViewById(R.id.illustration_lottie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val currentStage: Int
|
|
||||||
get() {
|
|
||||||
val progress = progressViewModel.progressLiveData.value!!
|
|
||||||
if (progress.steps == -1) {
|
|
||||||
return STAGE_UNKNOWN
|
|
||||||
}
|
|
||||||
val progressSteps: Int = progress.steps - progress.remaining
|
|
||||||
return if (progressSteps < getStageThresholdSteps(0)) {
|
|
||||||
STAGE_CENTER
|
|
||||||
} else if (progressSteps < getStageThresholdSteps(1)) {
|
|
||||||
STAGE_GUIDED
|
|
||||||
} else if (progressSteps < getStageThresholdSteps(2)) {
|
|
||||||
STAGE_FINGERTIP
|
|
||||||
} else if (progressSteps < getStageThresholdSteps(3)) {
|
|
||||||
STAGE_LEFT_EDGE
|
|
||||||
} else {
|
|
||||||
STAGE_RIGHT_EDGE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val isStageHalfCompleted: Boolean
|
|
||||||
get() {
|
|
||||||
val progress: EnrollmentProgress = progressViewModel.progressLiveData.value!!
|
|
||||||
if (progress.steps == -1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val progressSteps: Int = progress.steps - progress.remaining
|
|
||||||
var prevThresholdSteps = 0
|
|
||||||
for (i in 0 until enrollingViewModel.getEnrollStageCount()) {
|
|
||||||
val thresholdSteps = getStageThresholdSteps(i)
|
|
||||||
if (progressSteps in prevThresholdSteps until thresholdSteps) {
|
|
||||||
val adjustedProgress = progressSteps - prevThresholdSteps
|
|
||||||
val adjustedThreshold = thresholdSteps - prevThresholdSteps
|
|
||||||
return adjustedProgress >= adjustedThreshold / 2
|
|
||||||
}
|
|
||||||
prevThresholdSteps = thresholdSteps
|
|
||||||
}
|
|
||||||
|
|
||||||
// After last enrollment step.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getStageThresholdSteps(index: Int): Int {
|
|
||||||
val progress: EnrollmentProgress = progressViewModel.progressLiveData.value!!
|
|
||||||
if (progress.steps == -1) {
|
|
||||||
Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return (progress.steps * enrollingViewModel.getEnrollStageThreshold(index)).roundToInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun configureEnrollmentStage(@RawRes lottie: Int) {
|
|
||||||
subTitleText.text = ""
|
|
||||||
LottieCompositionFactory.fromRawRes(activity, lottie)
|
|
||||||
.addListener { c: LottieComposition ->
|
|
||||||
illustrationLottie?.let {
|
|
||||||
it.setComposition(c)
|
|
||||||
it.visibility = View.VISIBLE
|
|
||||||
it.playAnimation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentProgressChange(progress: EnrollmentProgress) {
|
|
||||||
updateProgress(true /* animate */, progress)
|
|
||||||
updateTitleAndDescription()
|
|
||||||
if (isAccessibilityEnabled) {
|
|
||||||
val steps: Int = progress.steps
|
|
||||||
val remaining: Int = progress.remaining
|
|
||||||
val percent = ((steps - remaining).toFloat() / steps.toFloat() * 100).toInt()
|
|
||||||
val announcement: CharSequence = activity!!.getString(
|
|
||||||
R.string.security_settings_udfps_enroll_progress_a11y_message, percent
|
|
||||||
)
|
|
||||||
enrollingViewModel.sendAccessibilityEvent(announcement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentHelp(helpMessage: EnrollmentStatusMessage) {
|
|
||||||
Log.d(TAG, "onEnrollmentHelp($helpMessage)")
|
|
||||||
val helpStr: CharSequence = helpMessage.str
|
|
||||||
if (helpStr.isNotEmpty()) {
|
|
||||||
showError(helpStr)
|
|
||||||
udfpsEnrollView.onEnrollmentHelp()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) {
|
|
||||||
cancelEnrollment(true)
|
|
||||||
lifecycleScope.launch {
|
|
||||||
Log.d(TAG, "newDialog $errorMessage")
|
|
||||||
errorDialogViewModel.newDialog(errorMessage.msgId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentCanceled(canceledSignal: Any) {
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal"
|
|
||||||
)
|
|
||||||
if (enrollingCancelSignal === canceledSignal) {
|
|
||||||
progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver)
|
|
||||||
progressViewModel.clearProgressLiveData()
|
|
||||||
if (enrollingViewModel.onBackPressed) {
|
|
||||||
enrollingViewModel.onCancelledDueToOnBackPressed()
|
|
||||||
} else if (enrollingViewModel.onSkipPressed) {
|
|
||||||
enrollingViewModel.onCancelledDueToOnSkipPressed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onAcquired(isAcquiredGood: Boolean) {
|
|
||||||
udfpsEnrollView.onAcquired(isAcquiredGood)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onPointerDown(sensorId: Int) {
|
|
||||||
udfpsEnrollView.onPointerDown(sensorId)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onPointerUp(sensorId: Int) {
|
|
||||||
udfpsEnrollView.onPointerUp(sensorId)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showError(error: CharSequence) {
|
|
||||||
titleText.text = error
|
|
||||||
titleText.contentDescription = error
|
|
||||||
subTitleText.contentDescription = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onRotationChanged(newRotation: Int) {
|
|
||||||
if ((newRotation + 2) % 4 == rotation) {
|
|
||||||
rotation = newRotation
|
|
||||||
requireContext().configLayout(newRotation, titleText, subTitleText, icon, skipBtn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val TAG = "FingerprintEnrollEnrollingUdfpsFragment"
|
|
||||||
private const val PROGRESS_BAR_MAX = 10000
|
|
||||||
private const val STAGE_UNKNOWN = -1
|
|
||||||
private const val STAGE_CENTER = 0
|
|
||||||
private const val STAGE_GUIDED = 1
|
|
||||||
private const val STAGE_FINGERTIP = 2
|
|
||||||
private const val STAGE_LEFT_EDGE = 3
|
|
||||||
private const val STAGE_RIGHT_EDGE = 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun FragmentActivity.bindFingerprintEnrollEnrollingUdfpsView(
|
|
||||||
view: RelativeLayout,
|
|
||||||
sensorProperties: FingerprintSensorPropertiesInternal,
|
|
||||||
@Surface.Rotation rotation: Int,
|
|
||||||
onSkipClickListener: View.OnClickListener
|
|
||||||
) {
|
|
||||||
view.findViewById<UdfpsEnrollView>(R.id.udfps_animation_view)!!.setSensorProperties(
|
|
||||||
sensorProperties
|
|
||||||
)
|
|
||||||
|
|
||||||
val titleText = view.findViewById<TextView>(R.id.suc_layout_title)!!
|
|
||||||
val subTitleText = view.findViewById<TextView>(R.id.sud_layout_subtitle)!!
|
|
||||||
val icon = view.findViewById<ImageView>(R.id.sud_layout_icon)!!
|
|
||||||
val skipBtn = view.findViewById<Button>(R.id.skip_btn)!!.also {
|
|
||||||
it.setOnClickListener(onSkipClickListener)
|
|
||||||
}
|
|
||||||
configLayout(rotation, titleText, subTitleText, icon, skipBtn)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Context.configLayout(
|
|
||||||
@Surface.Rotation newRotation: Int,
|
|
||||||
titleText: TextView,
|
|
||||||
subTitleText: TextView,
|
|
||||||
icon: ImageView,
|
|
||||||
skipBtn: Button
|
|
||||||
) {
|
|
||||||
if (newRotation == ROTATION_270) {
|
|
||||||
val iconLP = RelativeLayout.LayoutParams(-2, -2)
|
|
||||||
iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
|
|
||||||
iconLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view)
|
|
||||||
iconLP.topMargin = convertDpToPixel(76.64f)
|
|
||||||
iconLP.leftMargin = convertDpToPixel(151.54f)
|
|
||||||
icon.layoutParams = iconLP
|
|
||||||
val titleLP = RelativeLayout.LayoutParams(-1, -2)
|
|
||||||
titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
|
|
||||||
titleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view)
|
|
||||||
titleLP.topMargin = convertDpToPixel(138f)
|
|
||||||
titleLP.leftMargin = convertDpToPixel(144f)
|
|
||||||
titleText.layoutParams = titleLP
|
|
||||||
val subtitleLP = RelativeLayout.LayoutParams(-1, -2)
|
|
||||||
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
|
|
||||||
subtitleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view)
|
|
||||||
subtitleLP.topMargin = convertDpToPixel(198f)
|
|
||||||
subtitleLP.leftMargin = convertDpToPixel(144f)
|
|
||||||
subTitleText.layoutParams = subtitleLP
|
|
||||||
} else if (newRotation == ROTATION_90) {
|
|
||||||
val metrics = resources.displayMetrics
|
|
||||||
val iconLP = RelativeLayout.LayoutParams(-2, -2)
|
|
||||||
iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
|
|
||||||
iconLP.addRule(RelativeLayout.ALIGN_PARENT_START)
|
|
||||||
iconLP.topMargin = convertDpToPixel(76.64f)
|
|
||||||
iconLP.leftMargin = convertDpToPixel(71.99f)
|
|
||||||
icon.layoutParams = iconLP
|
|
||||||
val titleLP = RelativeLayout.LayoutParams(
|
|
||||||
metrics.widthPixels / 2, -2
|
|
||||||
)
|
|
||||||
titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
|
|
||||||
titleLP.addRule(RelativeLayout.ALIGN_PARENT_START, R.id.udfps_animation_view)
|
|
||||||
titleLP.topMargin = convertDpToPixel(138f)
|
|
||||||
titleLP.leftMargin = convertDpToPixel(66f)
|
|
||||||
titleText.layoutParams = titleLP
|
|
||||||
val subtitleLP = RelativeLayout.LayoutParams(
|
|
||||||
metrics.widthPixels / 2, -2
|
|
||||||
)
|
|
||||||
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
|
|
||||||
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_START)
|
|
||||||
subtitleLP.topMargin = convertDpToPixel(198f)
|
|
||||||
subtitleLP.leftMargin = convertDpToPixel(66f)
|
|
||||||
subTitleText.layoutParams = subtitleLP
|
|
||||||
}
|
|
||||||
if (newRotation == ROTATION_90 || newRotation == ROTATION_270) {
|
|
||||||
val skipBtnLP = skipBtn.layoutParams as RelativeLayout.LayoutParams
|
|
||||||
skipBtnLP.topMargin = convertDpToPixel(26f)
|
|
||||||
skipBtnLP.leftMargin = convertDpToPixel(54f)
|
|
||||||
skipBtn.requestLayout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.convertDpToPixel(dp: Float): Int {
|
|
||||||
return (dp * resources.displayMetrics.density).toInt()
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.view
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.hardware.biometrics.BiometricConstants
|
|
||||||
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS
|
|
||||||
import android.hardware.fingerprint.FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.android.settings.R
|
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog.getErrorMessage
|
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog.getErrorTitle
|
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog.getSetupErrorMessage
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintErrorDialogSetResultAction.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintErrorDialogSetResultAction.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fingerprint error dialog, will be shown when an error occurs during fingerprint enrollment.
|
|
||||||
*/
|
|
||||||
class FingerprintEnrollErrorDialog : DialogFragment() {
|
|
||||||
|
|
||||||
private val viewModel: FingerprintEnrollErrorDialogViewModel?
|
|
||||||
get() = activity?.let {
|
|
||||||
ViewModelProvider(it)[FingerprintEnrollErrorDialogViewModel::class.java]
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
val errorMsgId: Int = requireArguments().getInt(KEY_ERROR_MSG_ID)
|
|
||||||
val okButtonSetResultAction =
|
|
||||||
if (errorMsgId == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT)
|
|
||||||
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
|
|
||||||
else
|
|
||||||
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
|
|
||||||
return requireActivity().bindFingerprintEnrollEnrollingErrorDialog(
|
|
||||||
errorMsgId = errorMsgId,
|
|
||||||
isSuw = viewModel!!.isSuw,
|
|
||||||
tryAgainButtonClickListener = { dialog: DialogInterface?, _: Int ->
|
|
||||||
activity?.lifecycleScope?.launch {
|
|
||||||
Log.d(TAG, "tryAgain flow")
|
|
||||||
viewModel?.triggerRetry()
|
|
||||||
dialog?.dismiss()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
okButtonClickListener = { dialog: DialogInterface?, _: Int ->
|
|
||||||
activity?.lifecycleScope?.launch {
|
|
||||||
Log.d(TAG, "ok flow as $okButtonSetResultAction")
|
|
||||||
viewModel?.setResultAndFinish(okButtonSetResultAction)
|
|
||||||
dialog?.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "FingerprintEnrollErrorDialog"
|
|
||||||
private const val KEY_ERROR_MSG_ID = "error_msg_id"
|
|
||||||
|
|
||||||
fun newInstance(errorMsgId: Int): FingerprintEnrollErrorDialog {
|
|
||||||
val dialog = FingerprintEnrollErrorDialog()
|
|
||||||
val args = Bundle()
|
|
||||||
args.putInt(KEY_ERROR_MSG_ID, errorMsgId)
|
|
||||||
dialog.arguments = args
|
|
||||||
return dialog
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.bindFingerprintEnrollEnrollingErrorDialog(
|
|
||||||
errorMsgId: Int,
|
|
||||||
isSuw: Boolean,
|
|
||||||
tryAgainButtonClickListener: DialogInterface.OnClickListener,
|
|
||||||
okButtonClickListener: DialogInterface.OnClickListener
|
|
||||||
): AlertDialog = AlertDialog.Builder(this)
|
|
||||||
.setTitle(getString(getErrorTitle(errorMsgId)))
|
|
||||||
.setMessage(
|
|
||||||
getString(
|
|
||||||
if (isSuw)
|
|
||||||
getSetupErrorMessage(errorMsgId)
|
|
||||||
else
|
|
||||||
getErrorMessage(errorMsgId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.setCancelable(false).apply {
|
|
||||||
if (errorMsgId == FINGERPRINT_ERROR_UNABLE_TO_PROCESS) {
|
|
||||||
setPositiveButton(
|
|
||||||
R.string.security_settings_fingerprint_enroll_dialog_try_again,
|
|
||||||
tryAgainButtonClickListener
|
|
||||||
)
|
|
||||||
setNegativeButton(
|
|
||||||
R.string.security_settings_fingerprint_enroll_dialog_ok,
|
|
||||||
okButtonClickListener
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
setPositiveButton(
|
|
||||||
R.string.security_settings_fingerprint_enroll_dialog_ok,
|
|
||||||
okButtonClickListener
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
.apply { setCanceledOnTouchOutside(false) }
|
|
||||||
@@ -1,295 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.view
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.hardware.fingerprint.FingerprintManager.ENROLL_FIND_SENSOR
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.Surface
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import com.android.settings.R
|
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentProgress
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
|
|
||||||
import com.google.android.setupcompat.template.FooterBarMixin
|
|
||||||
import com.google.android.setupcompat.template.FooterButton
|
|
||||||
import com.google.android.setupdesign.GlifLayout
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragment explaining the side fingerprint sensor location for fingerprint enrollment.
|
|
||||||
* It interacts with ProgressViewModel, and FingerprintFindSensorAnimation.
|
|
||||||
* <pre>
|
|
||||||
* | Has | UDFPS | SFPS | Other (Rear FPS) |
|
|
||||||
* |---------------------|-------|------|------------------|
|
|
||||||
* | Primary button | Yes | No | No |
|
|
||||||
* | Illustration Lottie | Yes | Yes | No |
|
|
||||||
* | Animation | No | No | Depend on layout |
|
|
||||||
* | Progress ViewModel | No | Yes | Yes |
|
|
||||||
* | Orientation detect | No | Yes | No |
|
|
||||||
* | Foldable detect | No | Yes | No |
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
class FingerprintEnrollFindRfpsFragment : Fragment() {
|
|
||||||
|
|
||||||
private var _viewModel: FingerprintEnrollFindSensorViewModel? = null
|
|
||||||
private val viewModel: FingerprintEnrollFindSensorViewModel
|
|
||||||
get() = _viewModel!!
|
|
||||||
|
|
||||||
private var _progressViewModel: FingerprintEnrollProgressViewModel? = null
|
|
||||||
private val progressViewModel: FingerprintEnrollProgressViewModel
|
|
||||||
get() = _progressViewModel!!
|
|
||||||
|
|
||||||
private var _rotationViewModel: DeviceRotationViewModel? = null
|
|
||||||
private val rotationViewModel: DeviceRotationViewModel
|
|
||||||
get() = _rotationViewModel!!
|
|
||||||
|
|
||||||
private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null
|
|
||||||
private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel
|
|
||||||
get() = _errorDialogViewModel!!
|
|
||||||
|
|
||||||
private var findRfpsView: GlifLayout? = null
|
|
||||||
|
|
||||||
private val onSkipClickListener =
|
|
||||||
View.OnClickListener { _: View? -> viewModel.onSkipButtonClick() }
|
|
||||||
|
|
||||||
private var animation: FingerprintFindSensorAnimation? = null
|
|
||||||
|
|
||||||
private var enrollingCancelSignal: Any? = null
|
|
||||||
|
|
||||||
@Surface.Rotation
|
|
||||||
private var lastRotation = -1
|
|
||||||
|
|
||||||
private val progressObserver = Observer { progress: EnrollmentProgress? ->
|
|
||||||
if (progress != null && !progress.isInitialStep) {
|
|
||||||
cancelEnrollment(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val errorMessageObserver = Observer { errorMessage: EnrollmentStatusMessage? ->
|
|
||||||
Log.d(TAG, "errorMessageObserver($errorMessage)")
|
|
||||||
errorMessage?.let { onEnrollmentError(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val canceledSignalObserver = Observer { canceledSignal: Any? ->
|
|
||||||
canceledSignal?.let { onEnrollmentCanceled(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
findRfpsView = inflater.inflate(
|
|
||||||
R.layout.fingerprint_enroll_find_sensor,
|
|
||||||
container,
|
|
||||||
false
|
|
||||||
) as GlifLayout
|
|
||||||
|
|
||||||
val animationView = findRfpsView!!.findViewById<View>(
|
|
||||||
R.id.fingerprint_sensor_location_animation
|
|
||||||
)
|
|
||||||
if (animationView is FingerprintFindSensorAnimation) {
|
|
||||||
animation = animationView
|
|
||||||
}
|
|
||||||
|
|
||||||
return findRfpsView!!
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
requireActivity().bindFingerprintEnrollFindRfpsView(
|
|
||||||
view = findRfpsView!!,
|
|
||||||
onSkipClickListener = onSkipClickListener
|
|
||||||
)
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
errorDialogViewModel.triggerRetryFlow.collect { retryLookingForFingerprint() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun retryLookingForFingerprint() {
|
|
||||||
startEnrollment()
|
|
||||||
animation?.let {
|
|
||||||
Log.d(TAG, "retry, start animation")
|
|
||||||
it.startAnimation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
val isErrorDialogShown = errorDialogViewModel.isDialogShown
|
|
||||||
Log.d(TAG, "onStart(), isEnrolling:${progressViewModel.isEnrolling}"
|
|
||||||
+ ", isErrorDialog:$isErrorDialogShown")
|
|
||||||
if (!isErrorDialogShown) {
|
|
||||||
startEnrollment()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
val rotationLiveData: LiveData<Int> = rotationViewModel.liveData
|
|
||||||
lastRotation = rotationLiveData.value!!
|
|
||||||
if (!errorDialogViewModel.isDialogShown) {
|
|
||||||
animation?.let {
|
|
||||||
Log.d(TAG, "onResume(), start animation")
|
|
||||||
it.startAnimation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.onResume()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
animation?.let {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onPause(), pause animation")
|
|
||||||
}
|
|
||||||
it.pauseAnimation()
|
|
||||||
}
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
super.onStop()
|
|
||||||
removeEnrollmentObservers()
|
|
||||||
val isEnrolling = progressViewModel.isEnrolling
|
|
||||||
val isConfigChange = requireActivity().isChangingConfigurations
|
|
||||||
Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange")
|
|
||||||
if (isEnrolling && !isConfigChange) {
|
|
||||||
cancelEnrollment(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeEnrollmentObservers() {
|
|
||||||
progressViewModel.progressLiveData.removeObserver(progressObserver)
|
|
||||||
progressViewModel.helpMessageLiveData.removeObserver(errorMessageObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startEnrollment() {
|
|
||||||
enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_FIND_SENSOR)
|
|
||||||
if (enrollingCancelSignal == null) {
|
|
||||||
Log.e(TAG, "startEnrollment(), failed to start enrollment")
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "startEnrollment(), success")
|
|
||||||
}
|
|
||||||
progressViewModel.progressLiveData.observe(this, progressObserver)
|
|
||||||
progressViewModel.errorMessageLiveData.observe(this, errorMessageObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) {
|
|
||||||
if (!progressViewModel.isEnrolling) {
|
|
||||||
Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
removeEnrollmentObservers()
|
|
||||||
if (waitForLastCancelErrMsg) {
|
|
||||||
progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver)
|
|
||||||
} else {
|
|
||||||
enrollingCancelSignal = null
|
|
||||||
}
|
|
||||||
val cancelResult: Boolean = progressViewModel.cancelEnrollment()
|
|
||||||
if (!cancelResult) {
|
|
||||||
Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) {
|
|
||||||
cancelEnrollment(false)
|
|
||||||
lifecycleScope.launch {
|
|
||||||
Log.d(TAG, "newDialogFlow as $errorMessage")
|
|
||||||
errorDialogViewModel.newDialog(errorMessage.msgId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentCanceled(canceledSignal: Any) {
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal"
|
|
||||||
)
|
|
||||||
if (enrollingCancelSignal === canceledSignal) {
|
|
||||||
val progress: EnrollmentProgress? = progressViewModel.progressLiveData.value
|
|
||||||
progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver)
|
|
||||||
progressViewModel.clearProgressLiveData()
|
|
||||||
if (progress != null && !progress.isInitialStep) {
|
|
||||||
viewModel.onStartButtonClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
animation?.let {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onDestroy(), stop animation")
|
|
||||||
}
|
|
||||||
it.stopAnimation()
|
|
||||||
}
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
|
||||||
ViewModelProvider(requireActivity()).let { provider ->
|
|
||||||
_viewModel = provider[FingerprintEnrollFindSensorViewModel::class.java]
|
|
||||||
_progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
|
|
||||||
_rotationViewModel = provider[DeviceRotationViewModel::class.java]
|
|
||||||
_errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java]
|
|
||||||
}
|
|
||||||
super.onAttach(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val DEBUG = false
|
|
||||||
private const val TAG = "FingerprintEnrollFindRfpsFragment"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun FragmentActivity.bindFingerprintEnrollFindRfpsView(
|
|
||||||
view: GlifLayout,
|
|
||||||
onSkipClickListener: View.OnClickListener,
|
|
||||||
) {
|
|
||||||
GlifLayoutHelper(this, view).let {
|
|
||||||
it.setHeaderText(
|
|
||||||
R.string.security_settings_fingerprint_enroll_find_sensor_title
|
|
||||||
)
|
|
||||||
it.setDescriptionText(
|
|
||||||
getText(R.string.security_settings_fingerprint_enroll_find_sensor_message)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
view.getMixin(FooterBarMixin::class.java).secondaryButton =
|
|
||||||
FooterButton.Builder(this)
|
|
||||||
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
|
||||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
|
||||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
|
|
||||||
.build()
|
|
||||||
.also {
|
|
||||||
it.setOnClickListener(onSkipClickListener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.view
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.hardware.fingerprint.FingerprintManager.ENROLL_FIND_SENSOR
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.Surface
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.annotation.RawRes
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import com.airbnb.lottie.LottieAnimationView
|
|
||||||
import com.android.settings.R
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentProgress
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
|
|
||||||
import com.android.settingslib.widget.LottieColorUtils
|
|
||||||
import com.google.android.setupcompat.template.FooterBarMixin
|
|
||||||
import com.google.android.setupcompat.template.FooterButton
|
|
||||||
import com.google.android.setupdesign.GlifLayout
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragment explaining the side fingerprint sensor location for fingerprint enrollment.
|
|
||||||
* It interacts with ProgressViewModel, FoldCallback (for different lottie), and
|
|
||||||
* LottieAnimationView.
|
|
||||||
* <pre>
|
|
||||||
* | Has | UDFPS | SFPS | Other (Rear FPS) |
|
|
||||||
* |---------------------|-------|------|------------------|
|
|
||||||
* | Primary button | Yes | No | No |
|
|
||||||
* | Illustration Lottie | Yes | Yes | No |
|
|
||||||
* | Animation | No | No | Depend on layout |
|
|
||||||
* | Progress ViewModel | No | Yes | Yes |
|
|
||||||
* | Orientation detect | No | Yes | No |
|
|
||||||
* | Foldable detect | No | Yes | No |
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
class FingerprintEnrollFindSfpsFragment : Fragment() {
|
|
||||||
|
|
||||||
private var _viewModel: FingerprintEnrollFindSensorViewModel? = null
|
|
||||||
private val viewModel: FingerprintEnrollFindSensorViewModel
|
|
||||||
get() = _viewModel!!
|
|
||||||
|
|
||||||
private var _progressViewModel: FingerprintEnrollProgressViewModel? = null
|
|
||||||
private val progressViewModel: FingerprintEnrollProgressViewModel
|
|
||||||
get() = _progressViewModel!!
|
|
||||||
|
|
||||||
private var _rotationViewModel: DeviceRotationViewModel? = null
|
|
||||||
private val rotationViewModel: DeviceRotationViewModel
|
|
||||||
get() = _rotationViewModel!!
|
|
||||||
|
|
||||||
private var _foldedViewModel: DeviceFoldedViewModel? = null
|
|
||||||
private val foldedViewModel: DeviceFoldedViewModel
|
|
||||||
get() = _foldedViewModel!!
|
|
||||||
|
|
||||||
private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null
|
|
||||||
private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel
|
|
||||||
get() = _errorDialogViewModel!!
|
|
||||||
|
|
||||||
private var findSfpsView: GlifLayout? = null
|
|
||||||
|
|
||||||
private val onSkipClickListener =
|
|
||||||
View.OnClickListener { _: View? -> viewModel.onSkipButtonClick() }
|
|
||||||
|
|
||||||
private val illustrationLottie: LottieAnimationView
|
|
||||||
get() = findSfpsView!!.findViewById(R.id.illustration_lottie)!!
|
|
||||||
|
|
||||||
private var enrollingCancelSignal: Any? = null
|
|
||||||
|
|
||||||
@Surface.Rotation
|
|
||||||
private var animationRotation = -1
|
|
||||||
|
|
||||||
private val rotationObserver = Observer { rotation: Int? ->
|
|
||||||
rotation?.let { onRotationChanged(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val progressObserver = Observer { progress: EnrollmentProgress? ->
|
|
||||||
if (progress != null && !progress.isInitialStep) {
|
|
||||||
cancelEnrollment(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val errorMessageObserver = Observer{ errorMessage: EnrollmentStatusMessage? ->
|
|
||||||
Log.d(TAG, "errorMessageObserver($errorMessage)")
|
|
||||||
errorMessage?.let { onEnrollmentError(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val canceledSignalObserver = Observer { canceledSignal: Any? ->
|
|
||||||
canceledSignal?.let { onEnrollmentCanceled(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View = (inflater.inflate(
|
|
||||||
R.layout.sfps_enroll_find_sensor_layout,
|
|
||||||
container,
|
|
||||||
false
|
|
||||||
) as GlifLayout).also {
|
|
||||||
findSfpsView = it
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
requireActivity().bindFingerprintEnrollFindSfpsView(
|
|
||||||
view = findSfpsView!!,
|
|
||||||
onSkipClickListener = onSkipClickListener
|
|
||||||
)
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
errorDialogViewModel.triggerRetryFlow.collect { startEnrollment() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
val isErrorDialogShown = errorDialogViewModel.isDialogShown
|
|
||||||
Log.d(TAG, "onStart(), isEnrolling:${progressViewModel.isEnrolling}"
|
|
||||||
+ ", isErrorDialog:$isErrorDialogShown")
|
|
||||||
if (!isErrorDialogShown) {
|
|
||||||
startEnrollment()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
val rotationLiveData: LiveData<Int> = rotationViewModel.liveData
|
|
||||||
playLottieAnimation(rotationLiveData.value!!)
|
|
||||||
rotationLiveData.observe(this, rotationObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
rotationViewModel.liveData.removeObserver(rotationObserver)
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
super.onStop()
|
|
||||||
val isEnrolling = progressViewModel.isEnrolling
|
|
||||||
val isConfigChange = requireActivity().isChangingConfigurations
|
|
||||||
Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange")
|
|
||||||
if (isEnrolling && !isConfigChange) {
|
|
||||||
cancelEnrollment(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeEnrollmentObservers() {
|
|
||||||
progressViewModel.errorMessageLiveData.removeObserver(errorMessageObserver)
|
|
||||||
progressViewModel.progressLiveData.removeObserver(progressObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startEnrollment() {
|
|
||||||
enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_FIND_SENSOR)
|
|
||||||
if (enrollingCancelSignal == null) {
|
|
||||||
Log.e(TAG, "startEnrollment(), failed to start enrollment")
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "startEnrollment(), success")
|
|
||||||
}
|
|
||||||
progressViewModel.progressLiveData.observe(this, progressObserver)
|
|
||||||
progressViewModel.errorMessageLiveData.observe(this, errorMessageObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) {
|
|
||||||
if (!progressViewModel.isEnrolling) {
|
|
||||||
Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
removeEnrollmentObservers()
|
|
||||||
if (waitForLastCancelErrMsg) {
|
|
||||||
progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver)
|
|
||||||
} else {
|
|
||||||
enrollingCancelSignal = null
|
|
||||||
}
|
|
||||||
val cancelResult: Boolean = progressViewModel.cancelEnrollment()
|
|
||||||
if (!cancelResult) {
|
|
||||||
Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onRotationChanged(@Surface.Rotation newRotation: Int) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onRotationChanged() from $animationRotation to $newRotation")
|
|
||||||
}
|
|
||||||
if ((newRotation + 2) % 4 == animationRotation) {
|
|
||||||
// Fragment not changed, we just need to play correct rotation animation
|
|
||||||
playLottieAnimation(newRotation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) {
|
|
||||||
progressViewModel.cancelEnrollment()
|
|
||||||
lifecycleScope.launch {
|
|
||||||
Log.d(TAG, "newDialogFlow as $errorMessage")
|
|
||||||
errorDialogViewModel.newDialog(errorMessage.msgId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollmentCanceled(canceledSignal: Any) {
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal"
|
|
||||||
)
|
|
||||||
if (enrollingCancelSignal === canceledSignal) {
|
|
||||||
val progress: EnrollmentProgress? = progressViewModel.progressLiveData.value
|
|
||||||
progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver)
|
|
||||||
progressViewModel.clearProgressLiveData()
|
|
||||||
if (progress != null && !progress.isInitialStep) {
|
|
||||||
viewModel.onStartButtonClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun playLottieAnimation(@Surface.Rotation rotation: Int) {
|
|
||||||
@RawRes val animationRawRes = getSfpsLottieAnimationRawRes(rotation)
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"play lottie animation $animationRawRes, previous rotation:$animationRotation"
|
|
||||||
+ ", new rotation:" + rotation
|
|
||||||
)
|
|
||||||
animationRotation = rotation
|
|
||||||
illustrationLottie.setAnimation(animationRawRes)
|
|
||||||
LottieColorUtils.applyDynamicColors(activity, illustrationLottie)
|
|
||||||
illustrationLottie.visibility = View.VISIBLE
|
|
||||||
illustrationLottie.playAnimation()
|
|
||||||
}
|
|
||||||
|
|
||||||
@RawRes
|
|
||||||
private fun getSfpsLottieAnimationRawRes(@Surface.Rotation rotation: Int): Int {
|
|
||||||
val isFolded = java.lang.Boolean.FALSE != foldedViewModel.liveData.value
|
|
||||||
return when (rotation) {
|
|
||||||
Surface.ROTATION_90 ->
|
|
||||||
if (isFolded)
|
|
||||||
R.raw.fingerprint_edu_lottie_folded_top_left
|
|
||||||
else
|
|
||||||
R.raw.fingerprint_edu_lottie_portrait_top_left
|
|
||||||
Surface.ROTATION_180 ->
|
|
||||||
if (isFolded)
|
|
||||||
R.raw.fingerprint_edu_lottie_folded_bottom_left
|
|
||||||
else
|
|
||||||
R.raw.fingerprint_edu_lottie_landscape_bottom_left
|
|
||||||
Surface.ROTATION_270 ->
|
|
||||||
if (isFolded)
|
|
||||||
R.raw.fingerprint_edu_lottie_folded_bottom_right
|
|
||||||
else
|
|
||||||
R.raw.fingerprint_edu_lottie_portrait_bottom_right
|
|
||||||
else ->
|
|
||||||
if (isFolded)
|
|
||||||
R.raw.fingerprint_edu_lottie_folded_top_right
|
|
||||||
else
|
|
||||||
R.raw.fingerprint_edu_lottie_landscape_top_right
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
|
||||||
ViewModelProvider(requireActivity()).let { provider ->
|
|
||||||
_viewModel = provider[FingerprintEnrollFindSensorViewModel::class.java]
|
|
||||||
_progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
|
|
||||||
_rotationViewModel = provider[DeviceRotationViewModel::class.java]
|
|
||||||
_foldedViewModel = provider[DeviceFoldedViewModel::class.java]
|
|
||||||
_errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java]
|
|
||||||
}
|
|
||||||
super.onAttach(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val DEBUG = false
|
|
||||||
private const val TAG = "FingerprintEnrollFindSfpsFragment"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun FragmentActivity.bindFingerprintEnrollFindSfpsView(
|
|
||||||
view: GlifLayout,
|
|
||||||
onSkipClickListener: View.OnClickListener
|
|
||||||
) {
|
|
||||||
view.getMixin(FooterBarMixin::class.java).let {
|
|
||||||
it.secondaryButton = FooterButton.Builder(this)
|
|
||||||
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
|
||||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
|
||||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
|
|
||||||
.build()
|
|
||||||
it.secondaryButton.setOnClickListener(onSkipClickListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
GlifLayoutHelper(this, view).let {
|
|
||||||
it.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title)
|
|
||||||
it.setDescriptionText(
|
|
||||||
getText(R.string.security_settings_sfps_enroll_find_sensor_message)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.view
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import com.airbnb.lottie.LottieAnimationView
|
|
||||||
import com.android.settings.R
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel
|
|
||||||
import com.google.android.setupcompat.template.FooterBarMixin
|
|
||||||
import com.google.android.setupcompat.template.FooterButton
|
|
||||||
import com.google.android.setupdesign.GlifLayout
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragment explaining the under-display fingerprint sensor location for fingerprint enrollment.
|
|
||||||
* It interacts with Primary button, and LottieAnimationView.
|
|
||||||
* <pre>
|
|
||||||
* | Has | UDFPS | SFPS | Other (Rear FPS) |
|
|
||||||
* |---------------------|-------|------|------------------|
|
|
||||||
* | Primary button | Yes | No | No |
|
|
||||||
* | Illustration Lottie | Yes | Yes | No |
|
|
||||||
* | Animation | No | No | Depend on layout |
|
|
||||||
* | Progress ViewModel | No | Yes | Yes |
|
|
||||||
* | Orientation detect | No | Yes | No |
|
|
||||||
* | Foldable detect | No | Yes | No |
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
class FingerprintEnrollFindUdfpsFragment : Fragment() {
|
|
||||||
|
|
||||||
private var _viewModel: FingerprintEnrollFindSensorViewModel? = null
|
|
||||||
private val mViewModel: FingerprintEnrollFindSensorViewModel
|
|
||||||
get() = _viewModel!!
|
|
||||||
|
|
||||||
private var findUdfpsView: GlifLayout? = null
|
|
||||||
|
|
||||||
private val mOnSkipClickListener =
|
|
||||||
View.OnClickListener { _: View? -> mViewModel.onSkipButtonClick() }
|
|
||||||
|
|
||||||
private val mOnStartClickListener =
|
|
||||||
View.OnClickListener { _: View? -> mViewModel.onStartButtonClick() }
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View = (inflater.inflate(
|
|
||||||
R.layout.udfps_enroll_find_sensor_layout,
|
|
||||||
container,
|
|
||||||
false
|
|
||||||
) as GlifLayout).also {
|
|
||||||
findUdfpsView = it
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
requireActivity().bindFingerprintEnrollFindUdfpsView(
|
|
||||||
view = findUdfpsView!!,
|
|
||||||
isAccessibilityEnabled = mViewModel.isAccessibilityEnabled,
|
|
||||||
onSkipClickListener = mOnSkipClickListener,
|
|
||||||
onStartClickListener = mOnStartClickListener
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
|
||||||
_viewModel = ViewModelProvider(requireActivity())[
|
|
||||||
FingerprintEnrollFindSensorViewModel::class.java
|
|
||||||
]
|
|
||||||
super.onAttach(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun FragmentActivity.bindFingerprintEnrollFindUdfpsView(
|
|
||||||
view: GlifLayout,
|
|
||||||
isAccessibilityEnabled: Boolean,
|
|
||||||
onSkipClickListener: View.OnClickListener,
|
|
||||||
onStartClickListener: View.OnClickListener,
|
|
||||||
) {
|
|
||||||
GlifLayoutHelper(this, view).let { helper ->
|
|
||||||
helper.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title)
|
|
||||||
helper.setDescriptionText(
|
|
||||||
getText(R.string.security_settings_udfps_enroll_find_sensor_message)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
view.getMixin(FooterBarMixin::class.java)!!.let {
|
|
||||||
it.secondaryButton = FooterButton.Builder(this)
|
|
||||||
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
|
||||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
|
||||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
|
|
||||||
.build()
|
|
||||||
it.secondaryButton.setOnClickListener(onSkipClickListener)
|
|
||||||
|
|
||||||
it.primaryButton = FooterButton.Builder(this)
|
|
||||||
.setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
|
|
||||||
.setButtonType(FooterButton.ButtonType.NEXT)
|
|
||||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
|
||||||
.build()
|
|
||||||
it.primaryButton.setOnClickListener(onStartClickListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
view.findViewById<LottieAnimationView>(R.id.illustration_lottie)!!.let {
|
|
||||||
it.setOnClickListener(onStartClickListener)
|
|
||||||
if (isAccessibilityEnabled) {
|
|
||||||
it.setAnimation(R.raw.udfps_edu_a11y_lottie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.view
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import com.android.settings.R
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishViewModel
|
|
||||||
import com.google.android.setupcompat.template.FooterBarMixin
|
|
||||||
import com.google.android.setupcompat.template.FooterButton
|
|
||||||
import com.google.android.setupdesign.GlifLayout
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragment which concludes fingerprint enrollment.
|
|
||||||
*/
|
|
||||||
class FingerprintEnrollFinishFragment : Fragment() {
|
|
||||||
|
|
||||||
private var _viewModel: FingerprintEnrollFinishViewModel? = null
|
|
||||||
private val viewModel: FingerprintEnrollFinishViewModel
|
|
||||||
get() = _viewModel!!
|
|
||||||
|
|
||||||
private val addButtonClickListener =
|
|
||||||
View.OnClickListener { _: View? -> viewModel.onAddButtonClick() }
|
|
||||||
|
|
||||||
private val nextButtonClickListener =
|
|
||||||
View.OnClickListener { _: View? -> viewModel.onNextButtonClick() }
|
|
||||||
override fun onAttach(context: Context) {
|
|
||||||
super.onAttach(context)
|
|
||||||
_viewModel = ViewModelProvider(requireActivity())[
|
|
||||||
FingerprintEnrollFinishViewModel::class.java
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
) : View = (inflater.inflate(
|
|
||||||
if (viewModel.canAssumeSfps())
|
|
||||||
R.layout.sfps_enroll_finish
|
|
||||||
else
|
|
||||||
R.layout.fingerprint_enroll_finish,
|
|
||||||
container,
|
|
||||||
false
|
|
||||||
) as GlifLayout).also {
|
|
||||||
requireActivity().bindFingerprintEnrollFinishFragment(
|
|
||||||
view = it,
|
|
||||||
isSuw = viewModel.request.isSuw,
|
|
||||||
canAssumeSfps = viewModel.canAssumeSfps(),
|
|
||||||
isAnotherFingerprintEnrollable = viewModel.isAnotherFingerprintEnrollable,
|
|
||||||
nextButtonClickListener = nextButtonClickListener,
|
|
||||||
addButtonClickListener = addButtonClickListener
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun FragmentActivity.bindFingerprintEnrollFinishFragment(
|
|
||||||
view: GlifLayout,
|
|
||||||
isSuw: Boolean,
|
|
||||||
canAssumeSfps: Boolean,
|
|
||||||
isAnotherFingerprintEnrollable: Boolean,
|
|
||||||
nextButtonClickListener: View.OnClickListener,
|
|
||||||
addButtonClickListener: View.OnClickListener
|
|
||||||
) {
|
|
||||||
GlifLayoutHelper(this, view).apply {
|
|
||||||
setHeaderText(R.string.security_settings_fingerprint_enroll_finish_title)
|
|
||||||
setDescriptionText(
|
|
||||||
getString(
|
|
||||||
if (canAssumeSfps && isAnotherFingerprintEnrollable)
|
|
||||||
R.string.security_settings_fingerprint_enroll_finish_v2_add_fingerprint_message
|
|
||||||
else
|
|
||||||
R.string.security_settings_fingerprint_enroll_finish_v2_message
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
view.getMixin(FooterBarMixin::class.java).also { footer ->
|
|
||||||
footer.primaryButton = FooterButton.Builder(this)
|
|
||||||
.setText(
|
|
||||||
if (isSuw)
|
|
||||||
R.string.next_label
|
|
||||||
else
|
|
||||||
R.string.security_settings_fingerprint_enroll_done
|
|
||||||
)
|
|
||||||
.setListener(nextButtonClickListener)
|
|
||||||
.setButtonType(FooterButton.ButtonType.NEXT)
|
|
||||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
|
||||||
.build()
|
|
||||||
if (isAnotherFingerprintEnrollable) {
|
|
||||||
footer.secondaryButton = FooterButton.Builder(this)
|
|
||||||
.setText(R.string.fingerprint_enroll_button_add)
|
|
||||||
.setListener(addButtonClickListener)
|
|
||||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
|
||||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,329 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.view
|
|
||||||
|
|
||||||
import android.app.admin.DevicePolicyManager
|
|
||||||
import android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.PorterDuff
|
|
||||||
import android.graphics.PorterDuffColorFilter
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.Html
|
|
||||||
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.ScrollView
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import com.android.settings.R
|
|
||||||
import com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus
|
|
||||||
import com.android.settings.biometrics2.ui.model.FingerprintEnrollable.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
|
||||||
import com.android.settings.biometrics2.ui.model.FingerprintEnrollable.FINGERPRINT_ENROLLABLE_OK
|
|
||||||
import com.android.settings.biometrics2.ui.model.FingerprintEnrollable.FINGERPRINT_ENROLLABLE_UNKNOWN
|
|
||||||
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.DeviceHelper
|
|
||||||
import com.google.android.setupdesign.util.DynamicColorPalette
|
|
||||||
import com.google.android.setupdesign.util.DynamicColorPalette.ColorType.ACCENT
|
|
||||||
import java.util.function.Supplier
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fingerprint intro onboarding page fragment implementation
|
|
||||||
*/
|
|
||||||
class FingerprintEnrollIntroFragment : Fragment() {
|
|
||||||
|
|
||||||
private val viewModelProvider: ViewModelProvider
|
|
||||||
get() = ViewModelProvider(requireActivity())
|
|
||||||
|
|
||||||
private var _viewModel: FingerprintEnrollIntroViewModel? = null
|
|
||||||
private val viewModel: FingerprintEnrollIntroViewModel
|
|
||||||
get() = _viewModel!!
|
|
||||||
|
|
||||||
private var introView: GlifLayout? = null
|
|
||||||
|
|
||||||
private var primaryFooterButton: FooterButton? = null
|
|
||||||
|
|
||||||
private var secondaryFooterButton: FooterButton? = null
|
|
||||||
|
|
||||||
private val onNextClickListener =
|
|
||||||
View.OnClickListener { _: View? ->
|
|
||||||
activity?.lifecycleScope?.let {
|
|
||||||
viewModel.onNextButtonClick(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val onSkipOrCancelClickListener =
|
|
||||||
View.OnClickListener { _: View? ->
|
|
||||||
activity?.lifecycleScope?.let {
|
|
||||||
viewModel.onSkipOrCancelButtonClick(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
introView = inflater.inflate(
|
|
||||||
R.layout.fingerprint_enroll_introduction,
|
|
||||||
container,
|
|
||||||
false
|
|
||||||
) as GlifLayout
|
|
||||||
return introView!!
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
requireActivity().bindFingerprintEnrollIntroView(
|
|
||||||
view = introView!!,
|
|
||||||
canAssumeUdfps = viewModel.canAssumeUdfps,
|
|
||||||
isBiometricUnlockDisabledByAdmin = viewModel.isBiometricUnlockDisabledByAdmin,
|
|
||||||
isParentalConsentRequired = viewModel.isParentalConsentRequired,
|
|
||||||
descriptionDisabledByAdminSupplier = { getDescriptionDisabledByAdmin(view.context) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
val context: Context = requireContext()
|
|
||||||
val footerBarMixin: FooterBarMixin = footerBarMixin
|
|
||||||
viewModel.updateEnrollableStatus(lifecycleScope)
|
|
||||||
initPrimaryFooterButton(context, footerBarMixin)
|
|
||||||
initSecondaryFooterButton(context, footerBarMixin)
|
|
||||||
collectPageStatusFlowIfNeed()
|
|
||||||
super.onStart()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initPrimaryFooterButton(
|
|
||||||
context: Context,
|
|
||||||
footerBarMixin: FooterBarMixin
|
|
||||||
) {
|
|
||||||
if (footerBarMixin.primaryButton != null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
primaryFooterButton = FooterButton.Builder(context)
|
|
||||||
.setText(R.string.security_settings_fingerprint_enroll_introduction_agree)
|
|
||||||
.setButtonType(FooterButton.ButtonType.OPT_IN)
|
|
||||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
|
||||||
.build()
|
|
||||||
.also {
|
|
||||||
it.setOnClickListener(onNextClickListener)
|
|
||||||
footerBarMixin.primaryButton = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initSecondaryFooterButton(
|
|
||||||
context: Context,
|
|
||||||
footerBarMixin: FooterBarMixin
|
|
||||||
) {
|
|
||||||
if (footerBarMixin.secondaryButton != null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
secondaryFooterButton = FooterButton.Builder(context)
|
|
||||||
.setText(
|
|
||||||
if (viewModel.request.isAfterSuwOrSuwSuggestedAction)
|
|
||||||
R.string.security_settings_fingerprint_enroll_introduction_cancel
|
|
||||||
else
|
|
||||||
R.string.security_settings_fingerprint_enroll_introduction_no_thanks
|
|
||||||
)
|
|
||||||
.setButtonType(FooterButton.ButtonType.NEXT)
|
|
||||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
|
||||||
.build()
|
|
||||||
.also {
|
|
||||||
it.setOnClickListener(onSkipOrCancelClickListener)
|
|
||||||
footerBarMixin.setSecondaryButton(it, true /* usePrimaryStyle */)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun collectPageStatusFlowIfNeed() {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
val status = viewModel.pageStatusFlow.first()
|
|
||||||
Log.d(TAG, "collectPageStatusFlowIfNeed status:$status")
|
|
||||||
if (status.hasScrollToBottom()
|
|
||||||
|| status.enrollableStatus === FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
|
||||||
) {
|
|
||||||
// Update once and do not requireScrollWithButton() again when page has
|
|
||||||
// scrolled to bottom or User has enrolled at least a fingerprint, because if
|
|
||||||
// we requireScrollWithButton() again, primary button will become "More" after
|
|
||||||
// scrolling.
|
|
||||||
updateFooterButtons(status)
|
|
||||||
} else {
|
|
||||||
introView!!.getMixin(RequireScrollMixin::class.java).let {
|
|
||||||
it.requireScrollWithButton(
|
|
||||||
requireActivity(),
|
|
||||||
primaryFooterButton!!,
|
|
||||||
moreButtonTextRes,
|
|
||||||
onNextClickListener
|
|
||||||
)
|
|
||||||
it.setOnRequireScrollStateChangedListener { scrollNeeded: Boolean ->
|
|
||||||
viewModel.setHasScrolledToBottom(!scrollNeeded, lifecycleScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
viewModel.pageStatusFlow.collect(
|
|
||||||
this@FingerprintEnrollIntroFragment::updateFooterButtons
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
|
||||||
_viewModel = viewModelProvider[FingerprintEnrollIntroViewModel::class.java]
|
|
||||||
super.onAttach(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val footerBarMixin: FooterBarMixin
|
|
||||||
get() = introView!!.getMixin(FooterBarMixin::class.java)
|
|
||||||
|
|
||||||
private fun getDescriptionDisabledByAdmin(context: Context): String? {
|
|
||||||
val defaultStrId: Int =
|
|
||||||
R.string.security_settings_fingerprint_enroll_introduction_message_unlock_disabled
|
|
||||||
val devicePolicyManager: DevicePolicyManager =
|
|
||||||
checkNotNull(requireActivity().getSystemService(DevicePolicyManager::class.java))
|
|
||||||
|
|
||||||
return devicePolicyManager.resources.getString(FINGERPRINT_UNLOCK_DISABLED) {
|
|
||||||
context.getString(defaultStrId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateFooterButtons(status: FingerprintEnrollIntroStatus) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "updateFooterButtons($status)")
|
|
||||||
}
|
|
||||||
primaryFooterButton!!.setText(
|
|
||||||
context,
|
|
||||||
if (status.enrollableStatus === FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX)
|
|
||||||
R.string.done
|
|
||||||
else if (status.hasScrollToBottom())
|
|
||||||
R.string.security_settings_fingerprint_enroll_introduction_agree
|
|
||||||
else
|
|
||||||
moreButtonTextRes
|
|
||||||
)
|
|
||||||
secondaryFooterButton!!.visibility =
|
|
||||||
if (status.hasScrollToBottom()
|
|
||||||
&& status.enrollableStatus !== FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
|
||||||
)
|
|
||||||
View.VISIBLE
|
|
||||||
else
|
|
||||||
View.INVISIBLE
|
|
||||||
|
|
||||||
view!!.requireViewById<TextView>(R.id.error_text).let {
|
|
||||||
when (status.enrollableStatus) {
|
|
||||||
FINGERPRINT_ENROLLABLE_OK -> {
|
|
||||||
it.text = null
|
|
||||||
it.visibility = View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX -> {
|
|
||||||
it.setText(R.string.fingerprint_intro_error_max)
|
|
||||||
it.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
FINGERPRINT_ENROLLABLE_UNKNOWN -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@get:StringRes
|
|
||||||
private val moreButtonTextRes: Int
|
|
||||||
get() = R.string.security_settings_face_enroll_introduction_more
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "FingerprintEnrollIntroFragment"
|
|
||||||
private const val DEBUG = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun FragmentActivity.bindFingerprintEnrollIntroView(
|
|
||||||
view: GlifLayout,
|
|
||||||
canAssumeUdfps: Boolean,
|
|
||||||
isBiometricUnlockDisabledByAdmin: Boolean,
|
|
||||||
isParentalConsentRequired: Boolean,
|
|
||||||
descriptionDisabledByAdminSupplier: Supplier<String?>
|
|
||||||
) {
|
|
||||||
val context = view.context
|
|
||||||
|
|
||||||
val iconFingerprint = view.findViewById<ImageView>(R.id.icon_fingerprint)!!
|
|
||||||
val iconDeviceLocked = view.findViewById<ImageView>(R.id.icon_device_locked)!!
|
|
||||||
val iconTrashCan = view.findViewById<ImageView>(R.id.icon_trash_can)!!
|
|
||||||
val iconInfo = view.findViewById<ImageView>(R.id.icon_info)!!
|
|
||||||
val iconShield = view.findViewById<ImageView>(R.id.icon_shield)!!
|
|
||||||
val iconLink = view.findViewById<ImageView>(R.id.icon_link)!!
|
|
||||||
val footerMessage6 = view.findViewById<TextView>(R.id.footer_message_6)!!
|
|
||||||
|
|
||||||
PorterDuffColorFilter(
|
|
||||||
DynamicColorPalette.getColor(context, ACCENT),
|
|
||||||
PorterDuff.Mode.SRC_IN
|
|
||||||
).let { colorFilter ->
|
|
||||||
iconFingerprint.drawable.colorFilter = colorFilter
|
|
||||||
iconDeviceLocked.drawable.colorFilter = colorFilter
|
|
||||||
iconTrashCan.drawable.colorFilter = colorFilter
|
|
||||||
iconInfo.drawable.colorFilter = colorFilter
|
|
||||||
iconShield.drawable.colorFilter = colorFilter
|
|
||||||
iconLink.drawable.colorFilter = colorFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
view.findViewById<TextView>(R.id.footer_learn_more)!!.let { learnMore ->
|
|
||||||
learnMore.movementMethod = LinkMovementMethod.getInstance()
|
|
||||||
val footerLinkStr: String = context.getString(
|
|
||||||
R.string.security_settings_fingerprint_v2_enroll_introduction_message_learn_more,
|
|
||||||
Html.FROM_HTML_MODE_LEGACY
|
|
||||||
)
|
|
||||||
learnMore.text = Html.fromHtml(footerLinkStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canAssumeUdfps) {
|
|
||||||
footerMessage6.visibility = View.VISIBLE
|
|
||||||
iconShield.visibility = View.VISIBLE
|
|
||||||
} else {
|
|
||||||
footerMessage6.visibility = View.GONE
|
|
||||||
iconShield.visibility = View.GONE
|
|
||||||
}
|
|
||||||
val glifLayoutHelper = GlifLayoutHelper(this, view)
|
|
||||||
if (isBiometricUnlockDisabledByAdmin && !isParentalConsentRequired) {
|
|
||||||
glifLayoutHelper.setHeaderText(
|
|
||||||
R.string.security_settings_fingerprint_enroll_introduction_title_unlock_disabled
|
|
||||||
)
|
|
||||||
glifLayoutHelper.setDescriptionText(descriptionDisabledByAdminSupplier.get())
|
|
||||||
} else {
|
|
||||||
glifLayoutHelper.setHeaderText(
|
|
||||||
R.string.security_settings_fingerprint_enroll_introduction_title
|
|
||||||
)
|
|
||||||
glifLayoutHelper.setDescriptionText(
|
|
||||||
getString(
|
|
||||||
R.string.security_settings_fingerprint_enroll_introduction_v3_message,
|
|
||||||
DeviceHelper.getDeviceName(context)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
view.findViewById<ScrollView>(com.google.android.setupdesign.R.id.sud_scroll_view)
|
|
||||||
?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
|
|
||||||
}
|
|
||||||
@@ -1,633 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.view
|
|
||||||
|
|
||||||
import android.annotation.StyleRes
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.content.res.Resources.Theme
|
|
||||||
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.ActivityResultCallback
|
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
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.biometrics2.factory.BiometricsViewModelFactory
|
|
||||||
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR_KEY
|
|
||||||
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CREDENTIAL_MODEL_KEY
|
|
||||||
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.ENROLLMENT_REQUEST_KEY
|
|
||||||
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.CredentialAction
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_ICON_TOUCH_DIALOG
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_BACK_PRESSED
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_USER_SKIP
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FingerprintEnrollEnrollingAction
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FingerprintEnrollFindSensorAction
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishViewModel.FINGERPRINT_ENROLL_FINISH_ACTION_ADD_BUTTON_CLICK
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishViewModel.FINGERPRINT_ENROLL_FINISH_ACTION_NEXT_BUTTON_CLICK
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishViewModel.FingerprintEnrollFinishAction
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintErrorDialogSetResultAction.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintErrorDialogSetResultAction.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
|
|
||||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
|
||||||
import com.google.android.setupdesign.util.ThemeHelper
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fingerprint enrollment activity implementation
|
|
||||||
*/
|
|
||||||
open class FingerprintEnrollmentActivity : FragmentActivity() {
|
|
||||||
/** SetupWizard activity*/
|
|
||||||
class SetupActivity : FingerprintEnrollmentActivity()
|
|
||||||
|
|
||||||
/** Internal activity for FingerprintSettings */
|
|
||||||
class InternalActivity : FingerprintEnrollmentActivity()
|
|
||||||
|
|
||||||
private val viewModelProvider: ViewModelProvider by lazy {
|
|
||||||
ViewModelProvider(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val viewModel: FingerprintEnrollmentViewModel by lazy {
|
|
||||||
viewModelProvider[FingerprintEnrollmentViewModel::class.java]
|
|
||||||
}
|
|
||||||
|
|
||||||
private val autoCredentialViewModel: AutoCredentialViewModel by lazy {
|
|
||||||
viewModelProvider[AutoCredentialViewModel::class.java]
|
|
||||||
}
|
|
||||||
|
|
||||||
private val introViewModel: FingerprintEnrollIntroViewModel by lazy {
|
|
||||||
viewModelProvider[FingerprintEnrollIntroViewModel::class.java]
|
|
||||||
}
|
|
||||||
|
|
||||||
private val findSensorViewModel: FingerprintEnrollFindSensorViewModel by lazy {
|
|
||||||
viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java]
|
|
||||||
}
|
|
||||||
|
|
||||||
private val progressViewModel: FingerprintEnrollProgressViewModel by lazy {
|
|
||||||
viewModelProvider[FingerprintEnrollProgressViewModel::class.java]
|
|
||||||
}
|
|
||||||
|
|
||||||
private val enrollingViewModel: FingerprintEnrollEnrollingViewModel by lazy {
|
|
||||||
viewModelProvider[FingerprintEnrollEnrollingViewModel::class.java]
|
|
||||||
}
|
|
||||||
|
|
||||||
private val finishViewModel: FingerprintEnrollFinishViewModel by lazy {
|
|
||||||
viewModelProvider[FingerprintEnrollFinishViewModel::class.java]
|
|
||||||
}
|
|
||||||
|
|
||||||
private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel by lazy {
|
|
||||||
viewModelProvider[FingerprintEnrollErrorDialogViewModel::class.java]
|
|
||||||
}
|
|
||||||
|
|
||||||
private var isFirstFragmentAdded = false
|
|
||||||
|
|
||||||
private val findSensorActionObserver = Observer<Int?> { action ->
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "findSensorActionObserver($action)")
|
|
||||||
}
|
|
||||||
action?.let { onFindSensorAction(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val enrollingActionObserver = Observer<Int?> { action ->
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "enrollingActionObserver($action)")
|
|
||||||
}
|
|
||||||
action?.let { onEnrollingAction(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val finishActionObserver = Observer<Int?> { action ->
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "finishActionObserver($action)")
|
|
||||||
}
|
|
||||||
action?.let { onFinishAction(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val chooseLockResultCallback: ActivityResultCallback<ActivityResult> =
|
|
||||||
ActivityResultCallback { result ->
|
|
||||||
onChooseOrConfirmLockResult(true /* isChooseLock */, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val chooseLockLauncher: ActivityResultLauncher<Intent> =
|
|
||||||
registerForActivityResult(StartActivityForResult(), chooseLockResultCallback)
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
// Theme
|
|
||||||
setTheme(viewModel.request.theme)
|
|
||||||
ThemeHelper.trySetDynamicColor(this)
|
|
||||||
window.statusBarColor = Color.TRANSPARENT
|
|
||||||
|
|
||||||
// fragment
|
|
||||||
setContentView(R.layout.biometric_enrollment_container)
|
|
||||||
val fragment: Fragment? = supportFragmentManager.findFragmentById(
|
|
||||||
R.id.fragment_container_view
|
|
||||||
)
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"onCreate() has savedInstance:$(savedInstanceState != null), fragment:$fragment"
|
|
||||||
)
|
|
||||||
|
|
||||||
isFirstFragmentAdded = (savedInstanceState != null)
|
|
||||||
if (fragment == null) {
|
|
||||||
checkCredential()
|
|
||||||
if (viewModel.request.isSkipFindSensor) {
|
|
||||||
startEnrollingFragment()
|
|
||||||
} else if (viewModel.request.isSkipIntro) {
|
|
||||||
startFindSensorFragment()
|
|
||||||
} else {
|
|
||||||
startIntroFragment()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val tag: String? = fragment.tag
|
|
||||||
if (INTRO_TAG == tag) {
|
|
||||||
attachIntroViewModel()
|
|
||||||
} else if (FIND_SENSOR_TAG == tag) {
|
|
||||||
attachFindSensorViewModel()
|
|
||||||
attachIntroViewModel()
|
|
||||||
} else if (ENROLLING_TAG == tag) {
|
|
||||||
attachEnrollingViewModel()
|
|
||||||
attachFindSensorViewModel()
|
|
||||||
attachIntroViewModel()
|
|
||||||
} else if (FINISH_TAG == tag) {
|
|
||||||
attachFinishViewModel()
|
|
||||||
attachFindSensorViewModel()
|
|
||||||
attachIntroViewModel()
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "fragment tag $tag not found")
|
|
||||||
finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
collectFlows()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun collectFlows() {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
viewModel.setResultFlow.collect {
|
|
||||||
Log.d(TAG, "setResultLiveData($it)")
|
|
||||||
onSetActivityResult(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
autoCredentialViewModel.generateChallengeFailedFlow.collect {
|
|
||||||
Log.d(TAG, "generateChallengeFailedFlow($it)")
|
|
||||||
onSetActivityResult(ActivityResult(RESULT_CANCELED, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
errorDialogViewModel.newDialogFlow.collect {
|
|
||||||
Log.d(TAG, "newErrorDialogFlow($it)")
|
|
||||||
FingerprintEnrollErrorDialog.newInstance(it).show(
|
|
||||||
supportFragmentManager,
|
|
||||||
ERROR_DIALOG_TAG
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
errorDialogViewModel.setResultFlow.collect {
|
|
||||||
Log.d(TAG, "errorDialogSetResultFlow($it)")
|
|
||||||
when (it) {
|
|
||||||
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH -> onSetActivityResult(
|
|
||||||
ActivityResult(BiometricEnrollBase.RESULT_FINISHED, null)
|
|
||||||
)
|
|
||||||
|
|
||||||
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT -> onSetActivityResult(
|
|
||||||
ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startFragment(fragmentClass: Class<out Fragment>, tag: String) {
|
|
||||||
if (!isFirstFragmentAdded) {
|
|
||||||
supportFragmentManager.beginTransaction()
|
|
||||||
.setReorderingAllowed(true)
|
|
||||||
.replace(R.id.fragment_container_view, fragmentClass, null, tag)
|
|
||||||
.commit()
|
|
||||||
isFirstFragmentAdded = true
|
|
||||||
} else {
|
|
||||||
supportFragmentManager.beginTransaction()
|
|
||||||
.setReorderingAllowed(true)
|
|
||||||
.setCustomAnimations(
|
|
||||||
com.google.android.setupdesign.R.anim.shared_x_axis_activity_open_enter_dynamic_color,
|
|
||||||
com.google.android.setupdesign.R.anim.shared_x_axis_activity_open_exit,
|
|
||||||
com.google.android.setupdesign.R.anim.shared_x_axis_activity_close_enter_dynamic_color,
|
|
||||||
com.google.android.setupdesign.R.anim.shared_x_axis_activity_close_exit
|
|
||||||
)
|
|
||||||
.replace(R.id.fragment_container_view, fragmentClass, null, tag)
|
|
||||||
.addToBackStack(tag)
|
|
||||||
.commit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startIntroFragment() {
|
|
||||||
attachIntroViewModel()
|
|
||||||
startFragment(FingerprintEnrollIntroFragment::class.java, INTRO_TAG)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun attachIntroViewModel() {
|
|
||||||
val request: EnrollmentRequest = viewModel.request
|
|
||||||
if (request.isSkipIntro || request.isSkipFindSensor) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
introViewModel.actionFlow.collect(this@FingerprintEnrollmentActivity::onIntroAction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to make sure token is valid before entering find sensor page
|
|
||||||
private fun startFindSensorFragment() {
|
|
||||||
// Always setToken into progressViewModel even it is not necessary action for UDFPS
|
|
||||||
progressViewModel.setToken(autoCredentialViewModel.token)
|
|
||||||
attachFindSensorViewModel()
|
|
||||||
val fragmentClass: Class<out Fragment> = if (viewModel.canAssumeUdfps) {
|
|
||||||
FingerprintEnrollFindUdfpsFragment::class.java
|
|
||||||
} else if (viewModel.canAssumeSfps) {
|
|
||||||
FingerprintEnrollFindSfpsFragment::class.java
|
|
||||||
} else {
|
|
||||||
FingerprintEnrollFindRfpsFragment::class.java
|
|
||||||
}
|
|
||||||
startFragment(fragmentClass, FIND_SENSOR_TAG)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun attachFindSensorViewModel() {
|
|
||||||
if (viewModel.request.isSkipFindSensor) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
findSensorViewModel.let {
|
|
||||||
// Clear ActionLiveData in FragmentViewModel to prevent getting previous action during
|
|
||||||
// recreate, like press 'Start' then press 'back' in FingerprintEnrollEnrolling
|
|
||||||
// activity.
|
|
||||||
it.clearActionLiveData()
|
|
||||||
it.actionLiveData.observe(this, findSensorActionObserver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startEnrollingFragment() {
|
|
||||||
// Always setToken into progressViewModel even it is not necessary action for SFPS or RFPS
|
|
||||||
progressViewModel.setToken(autoCredentialViewModel.token)
|
|
||||||
attachEnrollingViewModel()
|
|
||||||
val fragmentClass: Class<out Fragment> = if (viewModel.canAssumeUdfps) {
|
|
||||||
FingerprintEnrollEnrollingUdfpsFragment::class.java
|
|
||||||
} else if (viewModel.canAssumeSfps) {
|
|
||||||
FingerprintEnrollEnrollingSfpsFragment::class.java
|
|
||||||
} else {
|
|
||||||
FingerprintEnrollEnrollingRfpsFragment::class.java
|
|
||||||
}
|
|
||||||
startFragment(fragmentClass, ENROLLING_TAG)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun attachEnrollingViewModel() {
|
|
||||||
enrollingViewModel.let {
|
|
||||||
it.clearActionLiveData()
|
|
||||||
it.actionLiveData.observe(this, enrollingActionObserver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startFinishFragment() {
|
|
||||||
viewModel.isNewFingerprintAdded = true
|
|
||||||
attachFinishViewModel()
|
|
||||||
if (viewModel.request.isSkipFindSensor) {
|
|
||||||
// Set page to Finish
|
|
||||||
supportFragmentManager.beginTransaction()
|
|
||||||
.setReorderingAllowed(true)
|
|
||||||
.setCustomAnimations(
|
|
||||||
com.google.android.setupdesign.R.anim.shared_x_axis_activity_open_enter_dynamic_color,
|
|
||||||
com.google.android.setupdesign.R.anim.shared_x_axis_activity_open_exit,
|
|
||||||
com.google.android.setupdesign.R.anim.shared_x_axis_activity_close_enter_dynamic_color,
|
|
||||||
com.google.android.setupdesign.R.anim.shared_x_axis_activity_close_exit
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
R.id.fragment_container_view,
|
|
||||||
FingerprintEnrollFinishFragment::class.java,
|
|
||||||
null,
|
|
||||||
FINISH_TAG
|
|
||||||
)
|
|
||||||
.commit()
|
|
||||||
} else {
|
|
||||||
// Remove Enrolling page
|
|
||||||
supportFragmentManager.popBackStack()
|
|
||||||
|
|
||||||
// Remove old Finish page if any
|
|
||||||
if (supportFragmentManager.findFragmentByTag(FINISH_TAG) != null) {
|
|
||||||
supportFragmentManager.popBackStack(FINISH_TAG, POP_BACK_STACK_INCLUSIVE)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove FindSensor page if maxEnrolled
|
|
||||||
if (viewModel.isMaxEnrolledReached(autoCredentialViewModel.userId)
|
|
||||||
&& supportFragmentManager.findFragmentByTag(FIND_SENSOR_TAG) != null
|
|
||||||
) {
|
|
||||||
supportFragmentManager.popBackStack(FIND_SENSOR_TAG, POP_BACK_STACK_INCLUSIVE)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add Finish page
|
|
||||||
supportFragmentManager.beginTransaction()
|
|
||||||
.setReorderingAllowed(true)
|
|
||||||
.setCustomAnimations(
|
|
||||||
com.google.android.setupdesign.R.anim.shared_x_axis_activity_open_enter_dynamic_color,
|
|
||||||
com.google.android.setupdesign.R.anim.shared_x_axis_activity_open_exit,
|
|
||||||
com.google.android.setupdesign.R.anim.shared_x_axis_activity_close_enter_dynamic_color,
|
|
||||||
com.google.android.setupdesign.R.anim.shared_x_axis_activity_close_exit
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
R.id.fragment_container_view,
|
|
||||||
FingerprintEnrollFinishFragment::class.java,
|
|
||||||
null,
|
|
||||||
FINISH_TAG
|
|
||||||
)
|
|
||||||
.addToBackStack(FINISH_TAG)
|
|
||||||
.commit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun attachFinishViewModel() {
|
|
||||||
finishViewModel.let {
|
|
||||||
it.clearActionLiveData()
|
|
||||||
it.actionLiveData.observe(this, finishActionObserver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onSetActivityResult(result: ActivityResult) {
|
|
||||||
val challengeExtras: Bundle? = autoCredentialViewModel.createGeneratingChallengeExtras()
|
|
||||||
val overrideResult: ActivityResult = viewModel.getOverrideActivityResult(
|
|
||||||
result, challengeExtras
|
|
||||||
)
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(
|
|
||||||
TAG, "onSetActivityResult(" + result + "), override:" + overrideResult
|
|
||||||
+ ") challengeExtras:" + challengeExtras
|
|
||||||
)
|
|
||||||
}
|
|
||||||
setResult(overrideResult.resultCode, overrideResult.data)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkCredential() {
|
|
||||||
when (autoCredentialViewModel.checkCredential(lifecycleScope)) {
|
|
||||||
CredentialAction.FAIL_NEED_TO_CHOOSE_LOCK -> {
|
|
||||||
val intent: Intent = autoCredentialViewModel.createChooseLockIntent(
|
|
||||||
this,
|
|
||||||
viewModel.request.isSuw,
|
|
||||||
viewModel.request.suwExtras
|
|
||||||
)
|
|
||||||
if (!viewModel.isWaitingActivityResult.compareAndSet(false, true)) {
|
|
||||||
Log.w(TAG, "chooseLock, fail to set isWaiting flag to true")
|
|
||||||
}
|
|
||||||
chooseLockLauncher.launch(intent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
CredentialAction.FAIL_NEED_TO_CONFIRM_LOCK -> {
|
|
||||||
val launched: Boolean = autoCredentialViewModel.createConfirmLockLauncher(
|
|
||||||
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 (!viewModel.isWaitingActivityResult.compareAndSet(false, true)) {
|
|
||||||
Log.w(TAG, "confirmLock, fail to set isWaiting flag to true")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
CredentialAction.CREDENTIAL_VALID,
|
|
||||||
CredentialAction.IS_GENERATING_CHALLENGE -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onChooseOrConfirmLockResult(
|
|
||||||
isChooseLock: Boolean,
|
|
||||||
activityResult: ActivityResult
|
|
||||||
) {
|
|
||||||
if (!viewModel.isWaitingActivityResult.compareAndSet(true, false)) {
|
|
||||||
Log.w(TAG, "isChooseLock:$isChooseLock, fail to unset waiting flag")
|
|
||||||
}
|
|
||||||
if (!autoCredentialViewModel.generateChallengeAsCredentialActivityResult(
|
|
||||||
isChooseLock,
|
|
||||||
activityResult,
|
|
||||||
lifecycleScope
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
onSetActivityResult(activityResult)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onIntroAction(action: FingerprintEnrollIntroAction) {
|
|
||||||
Log.d(TAG, "onIntroAction($action)")
|
|
||||||
when (action) {
|
|
||||||
FingerprintEnrollIntroAction.DONE_AND_FINISH -> {
|
|
||||||
onSetActivityResult(ActivityResult(BiometricEnrollBase.RESULT_FINISHED, null))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
FingerprintEnrollIntroAction.SKIP_OR_CANCEL -> {
|
|
||||||
onSetActivityResult(ActivityResult(BiometricEnrollBase.RESULT_SKIP, null))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
FingerprintEnrollIntroAction.CONTINUE_ENROLL -> {
|
|
||||||
startFindSensorFragment()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onFindSensorAction(@FingerprintEnrollFindSensorAction action: Int) {
|
|
||||||
when (action) {
|
|
||||||
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP -> {
|
|
||||||
onSetActivityResult(ActivityResult(BiometricEnrollBase.RESULT_SKIP, null))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG -> {
|
|
||||||
SkipSetupFindFpsDialog().show(
|
|
||||||
supportFragmentManager,
|
|
||||||
SKIP_SETUP_FIND_FPS_DIALOG_TAG
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START -> {
|
|
||||||
startEnrollingFragment()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onEnrollingAction(@FingerprintEnrollEnrollingAction action: Int) {
|
|
||||||
when (action) {
|
|
||||||
FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE -> {
|
|
||||||
startFinishFragment()
|
|
||||||
}
|
|
||||||
|
|
||||||
FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_USER_SKIP -> {
|
|
||||||
onSetActivityResult(ActivityResult(BiometricEnrollBase.RESULT_SKIP, null))
|
|
||||||
}
|
|
||||||
|
|
||||||
FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_ICON_TOUCH_DIALOG -> {
|
|
||||||
FingerprintEnrollEnrollingIconTouchDialog().show(
|
|
||||||
supportFragmentManager,
|
|
||||||
SKIP_SETUP_FIND_FPS_DIALOG_TAG
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_BACK_PRESSED -> {
|
|
||||||
if (supportFragmentManager.backStackEntryCount > 0) {
|
|
||||||
supportFragmentManager.popBackStack()
|
|
||||||
} else {
|
|
||||||
onSetActivityResult(ActivityResult(RESULT_CANCELED, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onFinishAction(@FingerprintEnrollFinishAction action: Int) {
|
|
||||||
when (action) {
|
|
||||||
FINGERPRINT_ENROLL_FINISH_ACTION_ADD_BUTTON_CLICK -> {
|
|
||||||
startEnrollingFragment()
|
|
||||||
}
|
|
||||||
|
|
||||||
FINGERPRINT_ENROLL_FINISH_ACTION_NEXT_BUTTON_CLICK -> {
|
|
||||||
val data: Intent? = if (viewModel.request.isSuw) {
|
|
||||||
Intent().also {
|
|
||||||
it.putExtras(
|
|
||||||
viewModel.getSuwFingerprintCountExtra(
|
|
||||||
autoCredentialViewModel.userId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
onSetActivityResult(ActivityResult(BiometricEnrollBase.RESULT_FINISHED, data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
viewModel.checkFinishActivityDuringOnPause(
|
|
||||||
isFinishing,
|
|
||||||
isChangingConfigurations,
|
|
||||||
lifecycleScope
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
viewModel.updateFingerprintSuggestionEnableState(autoCredentialViewModel.userId)
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onApplyThemeResource(theme: Theme, @StyleRes resid: Int, first: Boolean) {
|
|
||||||
theme.applyStyle(R.style.SetupWizardPartnerResource, true)
|
|
||||||
super.onApplyThemeResource(theme, resid, first)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
if (requestCode == LAUNCH_CONFIRM_LOCK_ACTIVITY) {
|
|
||||||
onChooseOrConfirmLockResult(false, ActivityResult(resultCode, data))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultViewModelCreationExtras: CreationExtras
|
|
||||||
get() = MutableCreationExtras(super.defaultViewModelCreationExtras).also {
|
|
||||||
it[CHALLENGE_GENERATOR_KEY] = FingerprintChallengeGenerator(
|
|
||||||
featureFactory.biometricsRepositoryProvider.getFingerprintRepository(application)!!
|
|
||||||
)
|
|
||||||
it[ENROLLMENT_REQUEST_KEY] =
|
|
||||||
EnrollmentRequest(intent, applicationContext, this is SetupActivity)
|
|
||||||
it[CREDENTIAL_MODEL_KEY] =
|
|
||||||
CredentialModel(intent.extras, SystemClock.elapsedRealtimeClock())
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultViewModelProviderFactory: ViewModelProvider.Factory
|
|
||||||
get() = BiometricsViewModelFactory()
|
|
||||||
|
|
||||||
override fun onAttachedToWindow() {
|
|
||||||
super.onAttachedToWindow()
|
|
||||||
window.statusBarColor = backgroundColor
|
|
||||||
}
|
|
||||||
|
|
||||||
@get:ColorInt
|
|
||||||
private val backgroundColor: Int
|
|
||||||
get() {
|
|
||||||
val stateList: ColorStateList? =
|
|
||||||
Utils.getColorAttr(this, android.R.attr.windowBackground)
|
|
||||||
return stateList?.defaultColor ?: Color.TRANSPARENT
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
|
||||||
viewModelProvider[DeviceFoldedViewModel::class.java].onConfigurationChanged(newConfig)
|
|
||||||
super.onConfigurationChanged(newConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val DEBUG = false
|
|
||||||
private const val TAG = "FingerprintEnrollmentActivity"
|
|
||||||
protected const val LAUNCH_CONFIRM_LOCK_ACTIVITY = 1
|
|
||||||
|
|
||||||
private const val INTRO_TAG = "intro"
|
|
||||||
private const val FIND_SENSOR_TAG = "find-sensor"
|
|
||||||
private const val ENROLLING_TAG = "enrolling"
|
|
||||||
private const val FINISH_TAG = "finish"
|
|
||||||
private const val SKIP_SETUP_FIND_FPS_DIALOG_TAG = "skip-setup-dialog"
|
|
||||||
private const val ERROR_DIALOG_TAG = "error-dialog"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.view
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.text.TextUtils
|
|
||||||
import android.view.View
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import com.google.android.setupdesign.GlifLayout
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utils class for GlifLayout
|
|
||||||
*/
|
|
||||||
class GlifLayoutHelper(val activity: Activity, val glifLayout: GlifLayout) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets header text to GlifLayout
|
|
||||||
*/
|
|
||||||
fun setHeaderText(@StringRes textResId: Int) {
|
|
||||||
val layoutTitle = glifLayout.headerTextView
|
|
||||||
val previousTitle = layoutTitle.text
|
|
||||||
val title = activity.getText(textResId)
|
|
||||||
if (previousTitle !== title) {
|
|
||||||
if (!TextUtils.isEmpty(previousTitle)) {
|
|
||||||
layoutTitle.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_POLITE
|
|
||||||
}
|
|
||||||
glifLayout.headerText = title
|
|
||||||
glifLayout.headerTextView.contentDescription = title
|
|
||||||
activity.title = title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets description text to GlifLayout
|
|
||||||
*/
|
|
||||||
fun setDescriptionText(description: CharSequence?) {
|
|
||||||
val previousDescription = glifLayout.descriptionText
|
|
||||||
// Prevent a11y for re-reading the same string
|
|
||||||
if (!TextUtils.equals(previousDescription, description)) {
|
|
||||||
glifLayout.descriptionText = description
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.view
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import com.android.settings.R
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skip dialog which shows when user clicks "Do it later" button in FingerprintFindSensor page.
|
|
||||||
*/
|
|
||||||
class SkipSetupFindFpsDialog : DialogFragment() {
|
|
||||||
|
|
||||||
private var mViewModel: FingerprintEnrollFindSensorViewModel? = null
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
|
|
||||||
requireActivity().bindSkipSetupFindFpsDialog {
|
|
||||||
_: DialogInterface?, _: Int -> mViewModel?.onSkipDialogButtonClick()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
|
||||||
mViewModel = ViewModelProvider(requireActivity())[
|
|
||||||
FingerprintEnrollFindSensorViewModel::class.java
|
|
||||||
]
|
|
||||||
super.onAttach(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.bindSkipSetupFindFpsDialog(
|
|
||||||
positiveButtonClickListener: DialogInterface.OnClickListener
|
|
||||||
): AlertDialog =
|
|
||||||
AlertDialog.Builder(this, R.style.Theme_AlertDialog)
|
|
||||||
.setTitle(R.string.setup_fingerprint_enroll_skip_title)
|
|
||||||
.setPositiveButton(R.string.skip_anyway_button_label, positiveButtonClickListener)
|
|
||||||
.setNegativeButton(R.string.go_back_button_label, null)
|
|
||||||
.setMessage(R.string.setup_fingerprint_enroll_skip_after_adding_lock_text)
|
|
||||||
.create()
|
|
||||||
@@ -1,283 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.Application
|
|
||||||
import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.result.ActivityResult
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import com.android.internal.widget.LockPatternUtils
|
|
||||||
import com.android.settings.biometrics.BiometricEnrollBase
|
|
||||||
import com.android.settings.biometrics.BiometricUtils
|
|
||||||
import com.android.settings.biometrics.BiometricUtils.GatekeeperCredentialNotMatchException
|
|
||||||
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 kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AutoCredentialViewModel which uses CredentialModel to determine next actions for activity, like
|
|
||||||
* start ChooseLockActivity, start ConfirmLockActivity, GenerateCredential, or do nothing.
|
|
||||||
*/
|
|
||||||
class AutoCredentialViewModel(
|
|
||||||
application: Application,
|
|
||||||
private val lockPatternUtils: LockPatternUtils,
|
|
||||||
private val challengeGenerator: ChallengeGenerator,
|
|
||||||
private val credentialModel: CredentialModel
|
|
||||||
) : AndroidViewModel(application) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic callback for FingerprintManager#generateChallenge or FaceManager#generateChallenge
|
|
||||||
*/
|
|
||||||
interface GenerateChallengeCallback {
|
|
||||||
/** Generic generateChallenge method for FingerprintManager or FaceManager */
|
|
||||||
fun onChallengeGenerated(sensorId: Int, userId: Int, challenge: Long)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A generic interface class for calling different generateChallenge from FingerprintManager or
|
|
||||||
* FaceManager
|
|
||||||
*/
|
|
||||||
interface ChallengeGenerator {
|
|
||||||
|
|
||||||
/** Callback that will be called later after challenge generated */
|
|
||||||
var callback: GenerateChallengeCallback?
|
|
||||||
|
|
||||||
/** Method for generating challenge from FingerprintManager or FaceManager */
|
|
||||||
fun generateChallenge(userId: Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Used to generate challenge through FingerprintRepository */
|
|
||||||
class FingerprintChallengeGenerator(
|
|
||||||
private val fingerprintRepository: FingerprintRepository
|
|
||||||
) : ChallengeGenerator {
|
|
||||||
|
|
||||||
override var callback: GenerateChallengeCallback? = null
|
|
||||||
|
|
||||||
override fun generateChallenge(userId: Int) {
|
|
||||||
callback?.let {
|
|
||||||
fingerprintRepository.generateChallenge(userId) {
|
|
||||||
sensorId: Int, uid: Int, challenge: Long ->
|
|
||||||
it.onChallengeGenerated(sensorId, uid, challenge)
|
|
||||||
}
|
|
||||||
} ?:run {
|
|
||||||
Log.e(TAG, "generateChallenge, null callback")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "FingerprintChallengeGenerator"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val _generateChallengeFailedFlow = MutableSharedFlow<Boolean>()
|
|
||||||
val generateChallengeFailedFlow: SharedFlow<Boolean>
|
|
||||||
get() = _generateChallengeFailedFlow.asSharedFlow()
|
|
||||||
|
|
||||||
|
|
||||||
// flag if token is generating through checkCredential()'s generateChallenge()
|
|
||||||
private var isGeneratingChallengeDuringCheckingCredential = false
|
|
||||||
|
|
||||||
/** Get bundle which passing back to FingerprintSettings for late generateChallenge() */
|
|
||||||
fun createGeneratingChallengeExtras(): Bundle? {
|
|
||||||
if (!isGeneratingChallengeDuringCheckingCredential
|
|
||||||
|| !credentialModel.isValidToken
|
|
||||||
|| !credentialModel.isValidChallenge
|
|
||||||
) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
val bundle = Bundle()
|
|
||||||
bundle.putByteArray(
|
|
||||||
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
|
|
||||||
credentialModel.token
|
|
||||||
)
|
|
||||||
bundle.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, credentialModel.challenge)
|
|
||||||
return bundle
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Check credential status for biometric enrollment. */
|
|
||||||
fun checkCredential(scope: CoroutineScope): CredentialAction {
|
|
||||||
return if (isValidCredential) {
|
|
||||||
CredentialAction.CREDENTIAL_VALID
|
|
||||||
} else if (isUnspecifiedPassword) {
|
|
||||||
CredentialAction.FAIL_NEED_TO_CHOOSE_LOCK
|
|
||||||
} else if (credentialModel.isValidGkPwHandle) {
|
|
||||||
val gkPwHandle = credentialModel.gkPwHandle
|
|
||||||
credentialModel.clearGkPwHandle()
|
|
||||||
// GkPwHandle is got through caller activity, we shall not revoke it after
|
|
||||||
// generateChallenge(). Let caller activity to make decision.
|
|
||||||
generateChallenge(gkPwHandle, false, scope)
|
|
||||||
isGeneratingChallengeDuringCheckingCredential = true
|
|
||||||
CredentialAction.IS_GENERATING_CHALLENGE
|
|
||||||
} else {
|
|
||||||
CredentialAction.FAIL_NEED_TO_CONFIRM_LOCK
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateChallenge(
|
|
||||||
gkPwHandle: Long,
|
|
||||||
revokeGkPwHandle: Boolean,
|
|
||||||
scope: CoroutineScope
|
|
||||||
) {
|
|
||||||
challengeGenerator.callback = object : GenerateChallengeCallback {
|
|
||||||
override fun onChallengeGenerated(sensorId: Int, userId: Int, challenge: Long) {
|
|
||||||
var illegalStateExceptionCaught = false
|
|
||||||
try {
|
|
||||||
val newToken = requestGatekeeperHat(gkPwHandle, challenge, userId)
|
|
||||||
credentialModel.challenge = challenge
|
|
||||||
credentialModel.token = newToken
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
Log.e(TAG, "generateChallenge, IllegalStateException", e)
|
|
||||||
illegalStateExceptionCaught = true
|
|
||||||
} finally {
|
|
||||||
if (revokeGkPwHandle) {
|
|
||||||
lockPatternUtils.removeGatekeeperPasswordHandle(gkPwHandle)
|
|
||||||
}
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"generateChallenge(), model:$credentialModel"
|
|
||||||
+ ", revokeGkPwHandle:$revokeGkPwHandle"
|
|
||||||
)
|
|
||||||
// Check credential again
|
|
||||||
if (!isValidCredential || illegalStateExceptionCaught) {
|
|
||||||
Log.w(TAG, "generateChallenge, invalid Credential or IllegalStateException")
|
|
||||||
scope.launch {
|
|
||||||
_generateChallengeFailedFlow.emit(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
challengeGenerator.generateChallenge(userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val isValidCredential: Boolean
|
|
||||||
get() = !isUnspecifiedPassword && credentialModel.isValidToken
|
|
||||||
|
|
||||||
private val isUnspecifiedPassword: Boolean
|
|
||||||
get() = lockPatternUtils.getActivePasswordQuality(userId) == 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 and viewModel is generating challenge
|
|
||||||
*/
|
|
||||||
fun generateChallengeAsCredentialActivityResult(
|
|
||||||
isChooseLock: Boolean,
|
|
||||||
result: ActivityResult,
|
|
||||||
scope: CoroutineScope
|
|
||||||
): Boolean {
|
|
||||||
if ((isChooseLock && result.resultCode == ChooseLockPattern.RESULT_FINISHED) ||
|
|
||||||
(!isChooseLock && result.resultCode == Activity.RESULT_OK)) {
|
|
||||||
result.data?.let {
|
|
||||||
val gkPwHandle = it.getLongExtra(
|
|
||||||
ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
|
|
||||||
CredentialModel.INVALID_GK_PW_HANDLE
|
|
||||||
)
|
|
||||||
// Revoke self requested GkPwHandle because it shall only used once inside this
|
|
||||||
// activity lifecycle.
|
|
||||||
generateChallenge(gkPwHandle, true, scope)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val userId: Int
|
|
||||||
get() = credentialModel.userId
|
|
||||||
|
|
||||||
val token: ByteArray?
|
|
||||||
get() = credentialModel.token
|
|
||||||
|
|
||||||
@Throws(IllegalStateException::class)
|
|
||||||
private fun requestGatekeeperHat(gkPwHandle: Long, challenge: Long, userId: Int): ByteArray? {
|
|
||||||
val response = lockPatternUtils
|
|
||||||
.verifyGatekeeperPasswordHandle(gkPwHandle, challenge, userId)
|
|
||||||
if (!response.isMatched) {
|
|
||||||
throw GatekeeperCredentialNotMatchException("Unable to request Gatekeeper HAT")
|
|
||||||
}
|
|
||||||
return response.gatekeeperHAT
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create Intent for choosing lock */
|
|
||||||
fun createChooseLockIntent(
|
|
||||||
context: Context, isSuw: Boolean,
|
|
||||||
suwExtras: Bundle
|
|
||||||
): Intent {
|
|
||||||
val 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)
|
|
||||||
if (credentialModel.isValidUserId) {
|
|
||||||
intent.putExtra(Intent.EXTRA_USER_ID, credentialModel.userId)
|
|
||||||
}
|
|
||||||
return intent
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create ConfirmLockLauncher */
|
|
||||||
fun createConfirmLockLauncher(
|
|
||||||
activity: Activity,
|
|
||||||
requestCode: Int, title: String
|
|
||||||
): ChooseLockSettingsHelper {
|
|
||||||
val builder = ChooseLockSettingsHelper.Builder(activity)
|
|
||||||
builder.setRequestCode(requestCode)
|
|
||||||
.setTitle(title)
|
|
||||||
.setRequestGatekeeperPasswordHandle(true)
|
|
||||||
.setForegroundOnly(true)
|
|
||||||
.setReturnCredentials(true)
|
|
||||||
if (credentialModel.isValidUserId) {
|
|
||||||
builder.setUserId(credentialModel.userId)
|
|
||||||
}
|
|
||||||
return builder.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "AutoCredentialViewModel"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class CredentialAction {
|
|
||||||
|
|
||||||
CREDENTIAL_VALID,
|
|
||||||
|
|
||||||
/** Valid credential, activity does nothing. */
|
|
||||||
IS_GENERATING_CHALLENGE,
|
|
||||||
|
|
||||||
/** This credential looks good, but still need to run generateChallenge(). */
|
|
||||||
FAIL_NEED_TO_CHOOSE_LOCK,
|
|
||||||
|
|
||||||
/** Need activity to run confirm lock */
|
|
||||||
FAIL_NEED_TO_CONFIRM_LOCK
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel;
|
|
||||||
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
|
|
||||||
import com.android.systemui.unfold.updates.FoldProvider;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ViewModel explaining the fingerprint sensor location for fingerprint enrollment.
|
|
||||||
*/
|
|
||||||
public class DeviceFoldedViewModel extends ViewModel {
|
|
||||||
|
|
||||||
private static final String TAG = "DeviceFoldedViewModel";
|
|
||||||
|
|
||||||
@NonNull private final MutableLiveData<Boolean> mLiveData =
|
|
||||||
new MutableLiveData<>(null);
|
|
||||||
|
|
||||||
private final ScreenSizeFoldProvider mScreenSizeFoldProvider;
|
|
||||||
private final FoldProvider.FoldCallback mIsFoldedCallback = isFolded -> {
|
|
||||||
Log.d(TAG, "onFoldUpdated= " + isFolded);
|
|
||||||
mLiveData.postValue(isFolded);
|
|
||||||
};
|
|
||||||
|
|
||||||
public DeviceFoldedViewModel(@NonNull ScreenSizeFoldProvider screenSizeFoldProvider,
|
|
||||||
@NonNull Executor executor) {
|
|
||||||
super();
|
|
||||||
mScreenSizeFoldProvider = screenSizeFoldProvider;
|
|
||||||
mScreenSizeFoldProvider.registerCallback(mIsFoldedCallback, executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls this method when activity gets configuration change
|
|
||||||
*/
|
|
||||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
|
||||||
mScreenSizeFoldProvider.onConfigurationChange(newConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns FoldedLiveData
|
|
||||||
*/
|
|
||||||
public LiveData<Boolean> getLiveData() {
|
|
||||||
return mLiveData;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCleared() {
|
|
||||||
mScreenSizeFoldProvider.unregisterCallback(mIsFoldedCallback);
|
|
||||||
super.onCleared();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel;
|
|
||||||
|
|
||||||
import static android.hardware.display.DisplayManager.DisplayListener;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.hardware.display.DisplayManager;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.DisplayInfo;
|
|
||||||
import android.view.Surface;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ViewModel explaining the fingerprint sensor location for fingerprint enrollment.
|
|
||||||
*/
|
|
||||||
public class DeviceRotationViewModel extends AndroidViewModel {
|
|
||||||
|
|
||||||
private static final boolean DEBUG = false;
|
|
||||||
private static final String TAG = "DeviceRotationViewModel";
|
|
||||||
|
|
||||||
private final DisplayManager mDisplayManager;
|
|
||||||
private final boolean mIsReverseDefaultRotation;
|
|
||||||
@NonNull private final DisplayInfo mDisplayInfo = new DisplayInfo();
|
|
||||||
|
|
||||||
/** {@link android.hardware.display.DisplayManager} is a final class, set this member visibility
|
|
||||||
* to 'protected' for testing
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
protected final DisplayListener mDisplayListener = new DisplayListener() {
|
|
||||||
@Override
|
|
||||||
public void onDisplayAdded(int displayId) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisplayRemoved(int displayId) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisplayChanged(int displayId) {
|
|
||||||
final int rotation = getRotation();
|
|
||||||
Log.d(TAG, "onDisplayChanged(" + displayId + "), rotation:" + rotation);
|
|
||||||
mLiveData.postValue(rotation);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@NonNull private final MutableLiveData<Integer> mLiveData = new MutableLiveData<>();
|
|
||||||
|
|
||||||
public DeviceRotationViewModel(@NonNull Application application) {
|
|
||||||
super(application);
|
|
||||||
mDisplayManager = application.getSystemService(DisplayManager.class);
|
|
||||||
mDisplayManager.registerDisplayListener(mDisplayListener,
|
|
||||||
application.getMainThreadHandler());
|
|
||||||
mIsReverseDefaultRotation = application.getResources().getBoolean(
|
|
||||||
com.android.internal.R.bool.config_reverseDefaultRotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns current rotation.
|
|
||||||
*
|
|
||||||
* {@link android.view.Display} is a final class, set this method visibility to "protected" for
|
|
||||||
* inheriting it in test
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
@Surface.Rotation
|
|
||||||
protected int getRotation() {
|
|
||||||
getApplication().getDisplay().getDisplayInfo(mDisplayInfo);
|
|
||||||
if (mIsReverseDefaultRotation) {
|
|
||||||
return (mDisplayInfo.rotation + 1) % 4;
|
|
||||||
} else {
|
|
||||||
return mDisplayInfo.rotation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns RotationLiveData
|
|
||||||
*/
|
|
||||||
public LiveData<Integer> getLiveData() {
|
|
||||||
final Integer lastRotation = mLiveData.getValue();
|
|
||||||
@Surface.Rotation int newRotation = getRotation();
|
|
||||||
if (lastRotation == null || lastRotation != newRotation) {
|
|
||||||
Log.d(TAG, "getLiveData, update rotation from " + lastRotation + " to " + newRotation);
|
|
||||||
mLiveData.setValue(newRotation);
|
|
||||||
}
|
|
||||||
return mLiveData;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCleared() {
|
|
||||||
mDisplayManager.unregisterDisplayListener(mDisplayListener);
|
|
||||||
super.onCleared();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel;
|
|
||||||
|
|
||||||
import android.annotation.IntDef;
|
|
||||||
import android.app.Application;
|
|
||||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
|
||||||
import android.os.VibrationAttributes;
|
|
||||||
import android.os.VibrationEffect;
|
|
||||||
import android.os.Vibrator;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
|
||||||
import android.view.accessibility.AccessibilityManager;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ViewModel explaining the fingerprint enrolling page
|
|
||||||
*/
|
|
||||||
public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel {
|
|
||||||
|
|
||||||
private static final String TAG = FingerprintEnrollEnrollingViewModel.class.getSimpleName();
|
|
||||||
private static final boolean DEBUG = false;
|
|
||||||
|
|
||||||
private static final VibrationEffect VIBRATE_EFFECT_ERROR =
|
|
||||||
VibrationEffect.createWaveform(new long[]{0, 5, 55, 60}, -1);
|
|
||||||
private static final VibrationAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES =
|
|
||||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enrolling finished
|
|
||||||
*/
|
|
||||||
public static final int FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Icon touch dialog show
|
|
||||||
*/
|
|
||||||
public static final int FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_ICON_TOUCH_DIALOG = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Has got latest cancelled event due to user skip
|
|
||||||
*/
|
|
||||||
public static final int FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_USER_SKIP = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Has got latest cancelled event due to back key
|
|
||||||
*/
|
|
||||||
public static final int FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_BACK_PRESSED = 3;
|
|
||||||
|
|
||||||
@IntDef(prefix = { "FINGERPRINT_ENROLL_ENROLLING_ACTION_" }, value = {
|
|
||||||
FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE,
|
|
||||||
FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_ICON_TOUCH_DIALOG,
|
|
||||||
FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_USER_SKIP
|
|
||||||
})
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
public @interface FingerprintEnrollEnrollingAction {}
|
|
||||||
|
|
||||||
private final int mUserId;
|
|
||||||
private boolean mOnBackPressed;
|
|
||||||
private boolean mOnSkipPressed;
|
|
||||||
@NonNull private final FingerprintRepository mFingerprintRepository;
|
|
||||||
private final AccessibilityManager mAccessibilityManager;
|
|
||||||
private final Vibrator mVibrator;
|
|
||||||
|
|
||||||
private final MutableLiveData<Integer> mActionLiveData = new MutableLiveData<>();
|
|
||||||
|
|
||||||
public FingerprintEnrollEnrollingViewModel(
|
|
||||||
@NonNull Application application,
|
|
||||||
int userId,
|
|
||||||
@NonNull FingerprintRepository fingerprintRepository
|
|
||||||
) {
|
|
||||||
super(application);
|
|
||||||
mUserId = userId;
|
|
||||||
mFingerprintRepository = fingerprintRepository;
|
|
||||||
mAccessibilityManager = application.getSystemService(AccessibilityManager.class);
|
|
||||||
mVibrator = application.getSystemService(Vibrator.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Integer> getActionLiveData() {
|
|
||||||
return mActionLiveData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears action live data
|
|
||||||
*/
|
|
||||||
public void clearActionLiveData() {
|
|
||||||
mActionLiveData.setValue(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getOnSkipPressed() {
|
|
||||||
return mOnSkipPressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User clicks skip button
|
|
||||||
*/
|
|
||||||
public void setOnSkipPressed() {
|
|
||||||
mOnSkipPressed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enrolling is cancelled because user clicks skip
|
|
||||||
*/
|
|
||||||
public void onCancelledDueToOnSkipPressed() {
|
|
||||||
final int action = FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_USER_SKIP;
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onSkipButtonClick, post action " + action);
|
|
||||||
}
|
|
||||||
mOnSkipPressed = false;
|
|
||||||
mActionLiveData.postValue(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is enrolling finished
|
|
||||||
*/
|
|
||||||
public void onEnrollingDone() {
|
|
||||||
final int action = FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE;
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onEnrollingDone, post action " + action);
|
|
||||||
}
|
|
||||||
mActionLiveData.postValue(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getOnBackPressed() {
|
|
||||||
return mOnBackPressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Back key is pressed.
|
|
||||||
*/
|
|
||||||
public void setOnBackPressed() {
|
|
||||||
mOnBackPressed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enrollment is cancelled because back key is pressed.
|
|
||||||
*/
|
|
||||||
public void onCancelledDueToOnBackPressed() {
|
|
||||||
final int action = FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_BACK_PRESSED;
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onCancelledEventReceivedAfterOnBackPressed, post action " + action);
|
|
||||||
}
|
|
||||||
mOnBackPressed = false;
|
|
||||||
mActionLiveData.postValue(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Icon touch dialog show
|
|
||||||
*/
|
|
||||||
public void showIconTouchDialog() {
|
|
||||||
final int action = FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_ICON_TOUCH_DIALOG;
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onIconTouchDialogShow, post action " + action);
|
|
||||||
}
|
|
||||||
mActionLiveData.postValue(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get enroll stage threshold
|
|
||||||
*/
|
|
||||||
public float getEnrollStageThreshold(int index) {
|
|
||||||
return mFingerprintRepository.getEnrollStageThreshold(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get enroll stage count
|
|
||||||
*/
|
|
||||||
public int getEnrollStageCount() {
|
|
||||||
return mFingerprintRepository.getEnrollStageCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests interruption of the accessibility feedback from all accessibility services.
|
|
||||||
*/
|
|
||||||
public void clearTalkback() {
|
|
||||||
mAccessibilityManager.interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns if the {@link AccessibilityManager} is enabled.
|
|
||||||
*
|
|
||||||
* @return True if this {@link AccessibilityManager} is enabled, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean isAccessibilityEnabled() {
|
|
||||||
return mAccessibilityManager.isEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an {@link AccessibilityEvent}.
|
|
||||||
*/
|
|
||||||
public void sendAccessibilityEvent(CharSequence announcement) {
|
|
||||||
AccessibilityEvent e = AccessibilityEvent.obtain();
|
|
||||||
e.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
|
|
||||||
e.setClassName(getClass().getName());
|
|
||||||
e.setPackageName(getApplication().getPackageName());
|
|
||||||
e.getText().add(announcement);
|
|
||||||
mAccessibilityManager.sendAccessibilityEvent(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns if the touch exploration in the system is enabled.
|
|
||||||
*
|
|
||||||
* @return True if touch exploration is enabled, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean isTouchExplorationEnabled() {
|
|
||||||
return mAccessibilityManager.isTouchExplorationEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like {@link #vibrate(VibrationEffect, VibrationAttributes)}, but allows the
|
|
||||||
* caller to specify the vibration is owned by someone else and set a reason for vibration.
|
|
||||||
*/
|
|
||||||
public void vibrateError(String reason) {
|
|
||||||
mVibrator.vibrate(mUserId, getApplication().getOpPackageName(),
|
|
||||||
VIBRATE_EFFECT_ERROR, reason, FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the first FingerprintSensorPropertiesInternal from FingerprintManager
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public FingerprintSensorPropertiesInternal getFirstFingerprintSensorPropertiesInternal() {
|
|
||||||
return mFingerprintRepository.getFirstFingerprintSensorPropertiesInternal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package com.android.settings.biometrics2.ui.viewmodel
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import kotlinx.atomicfu.AtomicBoolean
|
|
||||||
import kotlinx.atomicfu.atomic
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
|
||||||
|
|
||||||
class FingerprintEnrollErrorDialogViewModel(
|
|
||||||
application: Application,
|
|
||||||
val isSuw: Boolean
|
|
||||||
): AndroidViewModel(application) {
|
|
||||||
|
|
||||||
private val _isDialogShown: AtomicBoolean = atomic(false)
|
|
||||||
val isDialogShown: Boolean
|
|
||||||
get() = _isDialogShown.value
|
|
||||||
|
|
||||||
private val _newDialogFlow = MutableSharedFlow<Int>()
|
|
||||||
val newDialogFlow: SharedFlow<Int>
|
|
||||||
get() = _newDialogFlow.asSharedFlow()
|
|
||||||
|
|
||||||
private val _triggerRetryFlow = MutableSharedFlow<Any>()
|
|
||||||
val triggerRetryFlow: SharedFlow<Any>
|
|
||||||
get() = _triggerRetryFlow.asSharedFlow()
|
|
||||||
|
|
||||||
private val _setResultFlow = MutableSharedFlow<FingerprintErrorDialogSetResultAction>()
|
|
||||||
val setResultFlow: SharedFlow<FingerprintErrorDialogSetResultAction>
|
|
||||||
get() = _setResultFlow.asSharedFlow()
|
|
||||||
|
|
||||||
suspend fun newDialog(errorMsgId: Int) {
|
|
||||||
_isDialogShown.compareAndSet(expect = false, update = true)
|
|
||||||
_newDialogFlow.emit(errorMsgId)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun triggerRetry() {
|
|
||||||
_isDialogShown.compareAndSet(expect = true, update = false)
|
|
||||||
_triggerRetryFlow.emit(Any())
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun setResultAndFinish(action: FingerprintErrorDialogSetResultAction) {
|
|
||||||
_isDialogShown.compareAndSet(expect = true, update = false)
|
|
||||||
_setResultFlow.emit(action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class FingerprintErrorDialogSetResultAction {
|
|
||||||
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH,
|
|
||||||
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel;
|
|
||||||
|
|
||||||
import android.annotation.IntDef;
|
|
||||||
import android.app.Application;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.accessibility.AccessibilityManager;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ViewModel explaining the fingerprint sensor location for fingerprint enrollment.
|
|
||||||
*/
|
|
||||||
public class FingerprintEnrollFindSensorViewModel extends AndroidViewModel {
|
|
||||||
|
|
||||||
private static final boolean DEBUG = false;
|
|
||||||
private static final String TAG = "FingerprintEnrollFindSensorViewModel";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User clicks 'Skip' button on this page in Settings
|
|
||||||
*/
|
|
||||||
public static final int FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User clicks 'Skip' button on this page in SetupWizard flow
|
|
||||||
*/
|
|
||||||
public static final int FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User clicks 'Start' button on this page
|
|
||||||
*/
|
|
||||||
public static final int FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START = 2;
|
|
||||||
|
|
||||||
@IntDef(prefix = { "FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_" }, value = {
|
|
||||||
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP,
|
|
||||||
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG,
|
|
||||||
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START
|
|
||||||
})
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
public @interface FingerprintEnrollFindSensorAction {}
|
|
||||||
|
|
||||||
private final AccessibilityManager mAccessibilityManager;
|
|
||||||
|
|
||||||
private final boolean mIsSuw;
|
|
||||||
@NonNull private final MutableLiveData<Integer> mActionLiveData = new MutableLiveData<>();
|
|
||||||
|
|
||||||
public FingerprintEnrollFindSensorViewModel(@NonNull Application application, boolean isSuw) {
|
|
||||||
super(application);
|
|
||||||
mAccessibilityManager = application.getSystemService(AccessibilityManager.class);
|
|
||||||
mIsSuw = isSuw;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns action live data that user chooses
|
|
||||||
*/
|
|
||||||
public LiveData<Integer> getActionLiveData() {
|
|
||||||
return mActionLiveData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear ActionLiveData to prevent get obsolete data
|
|
||||||
*/
|
|
||||||
public void clearActionLiveData() {
|
|
||||||
mActionLiveData.setValue(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User clicks skip button on dialog
|
|
||||||
*/
|
|
||||||
public void onSkipDialogButtonClick() {
|
|
||||||
final int action = FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP;
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onSkipDialogButtonClick, post " + action);
|
|
||||||
}
|
|
||||||
mActionLiveData.postValue(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User clicks skip button
|
|
||||||
*/
|
|
||||||
public void onSkipButtonClick() {
|
|
||||||
final int action = mIsSuw
|
|
||||||
? FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG
|
|
||||||
: FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP;
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onSkipButtonClick, post action " + action);
|
|
||||||
}
|
|
||||||
mActionLiveData.postValue(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User clicks start button
|
|
||||||
*/
|
|
||||||
public void onStartButtonClick() {
|
|
||||||
final int action = FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START;
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onStartButtonClick, post action " + action);
|
|
||||||
}
|
|
||||||
mActionLiveData.postValue(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the info about accessibility is enabled or not
|
|
||||||
*/
|
|
||||||
public boolean isAccessibilityEnabled() {
|
|
||||||
return mAccessibilityManager.isEnabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel;
|
|
||||||
|
|
||||||
import android.annotation.IntDef;
|
|
||||||
import android.app.Application;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
|
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finish ViewModel handles the state of the fingerprint renroll final stage
|
|
||||||
*/
|
|
||||||
public class FingerprintEnrollFinishViewModel extends AndroidViewModel {
|
|
||||||
|
|
||||||
private static final String TAG = FingerprintEnrollFinishViewModel.class.getSimpleName();
|
|
||||||
private static final boolean DEBUG = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User clicks "Add" button
|
|
||||||
*/
|
|
||||||
public static final int FINGERPRINT_ENROLL_FINISH_ACTION_ADD_BUTTON_CLICK = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User clicks "Next" button
|
|
||||||
*/
|
|
||||||
public static final int FINGERPRINT_ENROLL_FINISH_ACTION_NEXT_BUTTON_CLICK = 1;
|
|
||||||
|
|
||||||
@IntDef(prefix = { "FINGERPRINT_ENROLL_FINISH_ACTION_" }, value = {
|
|
||||||
FINGERPRINT_ENROLL_FINISH_ACTION_ADD_BUTTON_CLICK,
|
|
||||||
FINGERPRINT_ENROLL_FINISH_ACTION_NEXT_BUTTON_CLICK
|
|
||||||
})
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
public @interface FingerprintEnrollFinishAction {}
|
|
||||||
|
|
||||||
@NonNull private final FingerprintRepository mFingerprintRepository;
|
|
||||||
@NonNull private final EnrollmentRequest mRequest;
|
|
||||||
private final int mUserId;
|
|
||||||
|
|
||||||
private final MutableLiveData<Integer> mActionLiveData = new MutableLiveData<>();
|
|
||||||
|
|
||||||
public FingerprintEnrollFinishViewModel(@NonNull Application application, int userId,
|
|
||||||
@NonNull EnrollmentRequest request,
|
|
||||||
@NonNull FingerprintRepository fingerprintRepository) {
|
|
||||||
super(application);
|
|
||||||
mUserId = userId;
|
|
||||||
mRequest = request;
|
|
||||||
mFingerprintRepository = fingerprintRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public EnrollmentRequest getRequest() {
|
|
||||||
return mRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The first sensor type is Side fps sensor or not
|
|
||||||
*/
|
|
||||||
public boolean canAssumeSfps() {
|
|
||||||
return mFingerprintRepository.canAssumeSfps();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Device allows user to enroll another fingerprint or not.
|
|
||||||
*/
|
|
||||||
public boolean isAnotherFingerprintEnrollable() {
|
|
||||||
return mFingerprintRepository.getNumOfEnrolledFingerprintsSize(mUserId)
|
|
||||||
< mFingerprintRepository.getMaxFingerprints();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear action LiveData
|
|
||||||
*/
|
|
||||||
public void clearActionLiveData() {
|
|
||||||
mActionLiveData.setValue(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get action LiveData
|
|
||||||
*/
|
|
||||||
public LiveData<Integer> getActionLiveData() {
|
|
||||||
return mActionLiveData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle add button Click
|
|
||||||
*/
|
|
||||||
public void onAddButtonClick() {
|
|
||||||
final int action = FINGERPRINT_ENROLL_FINISH_ACTION_ADD_BUTTON_CLICK;
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onAddButtonClick post(" + action + ")");
|
|
||||||
}
|
|
||||||
mActionLiveData.postValue(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle next button Click
|
|
||||||
*/
|
|
||||||
public void onNextButtonClick() {
|
|
||||||
final int action = FINGERPRINT_ENROLL_FINISH_ACTION_NEXT_BUTTON_CLICK;
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onNextButtonClick post(" + action + ")");
|
|
||||||
}
|
|
||||||
mActionLiveData.postValue(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
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.biometrics2.ui.model.FingerprintEnrollable
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction.CONTINUE_ENROLL
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction.DONE_AND_FINISH
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction.SKIP_OR_CANCEL
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/** Fingerprint intro onboarding page view model implementation */
|
|
||||||
class FingerprintEnrollIntroViewModel(
|
|
||||||
application: Application,
|
|
||||||
private val fingerprintRepository: FingerprintRepository,
|
|
||||||
val request: EnrollmentRequest,
|
|
||||||
private val userId: Int
|
|
||||||
) : AndroidViewModel(application) {
|
|
||||||
|
|
||||||
/** User's action flow (like clicking Agree, Skip, or Done) */
|
|
||||||
private val _actionFlow = MutableSharedFlow<FingerprintEnrollIntroAction>()
|
|
||||||
val actionFlow: SharedFlow<FingerprintEnrollIntroAction>
|
|
||||||
get() = _actionFlow.asSharedFlow()
|
|
||||||
|
|
||||||
private fun getEnrollableStatus(): FingerprintEnrollable {
|
|
||||||
val num = fingerprintRepository.getNumOfEnrolledFingerprintsSize(userId)
|
|
||||||
val max =
|
|
||||||
if (request.isSuw && !request.isAfterSuwOrSuwSuggestedAction)
|
|
||||||
fingerprintRepository.getMaxFingerprintsInSuw(
|
|
||||||
getApplication<Application>().resources
|
|
||||||
)
|
|
||||||
else
|
|
||||||
fingerprintRepository.maxFingerprints
|
|
||||||
return if (num >= max)
|
|
||||||
FingerprintEnrollable.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
|
||||||
else
|
|
||||||
FingerprintEnrollable.FINGERPRINT_ENROLLABLE_OK
|
|
||||||
}
|
|
||||||
|
|
||||||
private val hasScrolledToBottomFlow = MutableStateFlow(HAS_SCROLLED_TO_BOTTOM_DEFAULT)
|
|
||||||
private val enrollableStatusFlow = MutableStateFlow(getEnrollableStatus())
|
|
||||||
|
|
||||||
/** Enrollable status and hasScrollToBottom live data */
|
|
||||||
val pageStatusFlow: Flow<FingerprintEnrollIntroStatus> =
|
|
||||||
hasScrolledToBottomFlow.combine(enrollableStatusFlow) {
|
|
||||||
hasScrolledToBottom: Boolean, enrollableStatus: FingerprintEnrollable ->
|
|
||||||
FingerprintEnrollIntroStatus(hasScrolledToBottom, enrollableStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateEnrollableStatus(scope: CoroutineScope) {
|
|
||||||
scope.launch {
|
|
||||||
enrollableStatusFlow.emit(getEnrollableStatus())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The first sensor type is UDFPS sensor or not */
|
|
||||||
val canAssumeUdfps: Boolean
|
|
||||||
get() = fingerprintRepository.canAssumeUdfps()
|
|
||||||
|
|
||||||
/** Update onboarding intro page has scrolled to bottom */
|
|
||||||
fun setHasScrolledToBottom(value: Boolean, scope: CoroutineScope) {
|
|
||||||
scope.launch {
|
|
||||||
hasScrolledToBottomFlow.emit(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get parental consent required or not during enrollment process */
|
|
||||||
val isParentalConsentRequired: Boolean
|
|
||||||
get() = fingerprintRepository.isParentalConsentRequired(getApplication())
|
|
||||||
|
|
||||||
/** Get fingerprint is disable by admin or not */
|
|
||||||
val isBiometricUnlockDisabledByAdmin: Boolean
|
|
||||||
get() = fingerprintRepository.isDisabledByAdmin(getApplication(), userId)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User clicks next button
|
|
||||||
*/
|
|
||||||
fun onNextButtonClick(scope: CoroutineScope) {
|
|
||||||
scope.launch {
|
|
||||||
when (val status = enrollableStatusFlow.value) {
|
|
||||||
FingerprintEnrollable.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX ->
|
|
||||||
_actionFlow.emit(DONE_AND_FINISH)
|
|
||||||
|
|
||||||
FingerprintEnrollable.FINGERPRINT_ENROLLABLE_OK ->
|
|
||||||
_actionFlow.emit(CONTINUE_ENROLL)
|
|
||||||
|
|
||||||
else -> Log.w(TAG, "fail to click next, enrolled:$status")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** User clicks skip/cancel button */
|
|
||||||
fun onSkipOrCancelButtonClick(scope: CoroutineScope) {
|
|
||||||
scope.launch {
|
|
||||||
_actionFlow.emit(SKIP_OR_CANCEL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "FingerprintEnrollIntroViewModel"
|
|
||||||
private const val HAS_SCROLLED_TO_BOTTOM_DEFAULT = false
|
|
||||||
private val ENROLLABLE_STATUS_DEFAULT = FingerprintEnrollable.FINGERPRINT_ENROLLABLE_UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class FingerprintEnrollIntroAction {
|
|
||||||
/** User clicks 'Done' button on this page */
|
|
||||||
DONE_AND_FINISH,
|
|
||||||
/** User clicks 'Agree' button on this page */
|
|
||||||
CONTINUE_ENROLL,
|
|
||||||
/** User clicks 'Skip' button on this page */
|
|
||||||
SKIP_OR_CANCEL
|
|
||||||
}
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel;
|
|
||||||
|
|
||||||
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED;
|
|
||||||
import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL;
|
|
||||||
|
|
||||||
import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_REMAINING;
|
|
||||||
import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_STEPS;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.hardware.fingerprint.FingerprintManager.EnrollReason;
|
|
||||||
import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
|
|
||||||
import android.os.CancellationSignal;
|
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintUpdater;
|
|
||||||
import com.android.settings.biometrics.fingerprint.MessageDisplayController;
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Progress ViewModel handles the state around biometric enrollment. It manages the state of
|
|
||||||
* enrollment throughout the activity lifecycle so the app can continue after an event like
|
|
||||||
* rotation.
|
|
||||||
*/
|
|
||||||
public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
|
|
||||||
|
|
||||||
private static final boolean DEBUG = false;
|
|
||||||
private static final String TAG = "FingerprintEnrollProgressViewModel";
|
|
||||||
|
|
||||||
private final MutableLiveData<EnrollmentProgress> mProgressLiveData = new MutableLiveData<>(
|
|
||||||
new EnrollmentProgress(INITIAL_STEPS, INITIAL_REMAINING));
|
|
||||||
private final MutableLiveData<EnrollmentStatusMessage> mHelpMessageLiveData =
|
|
||||||
new MutableLiveData<>();
|
|
||||||
private final MutableLiveData<EnrollmentStatusMessage> mErrorMessageLiveData =
|
|
||||||
new MutableLiveData<>();
|
|
||||||
private final MutableLiveData<Object> mCanceledSignalLiveData = new MutableLiveData<>();
|
|
||||||
private final MutableLiveData<Boolean> mAcquireLiveData = new MutableLiveData<>();
|
|
||||||
private final MutableLiveData<Integer> mPointerDownLiveData = new MutableLiveData<>();
|
|
||||||
private final MutableLiveData<Integer> mPointerUpLiveData = new MutableLiveData<>();
|
|
||||||
|
|
||||||
private byte[] mToken = null;
|
|
||||||
private final int mUserId;
|
|
||||||
|
|
||||||
private final FingerprintUpdater mFingerprintUpdater;
|
|
||||||
@Nullable private CancellationSignal mCancellationSignal = null;
|
|
||||||
@NonNull private final LinkedList<CancellationSignal> mCancelingSignalQueue =
|
|
||||||
new LinkedList<>();
|
|
||||||
private final EnrollmentCallback mEnrollmentCallback = new EnrollmentCallback() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnrollmentProgress(int remaining) {
|
|
||||||
final int currentSteps = getSteps();
|
|
||||||
final EnrollmentProgress progress = new EnrollmentProgress(
|
|
||||||
currentSteps == INITIAL_STEPS ? remaining : getSteps(), remaining);
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onEnrollmentProgress(" + remaining + "), steps: " + currentSteps
|
|
||||||
+ ", post progress as " + progress);
|
|
||||||
}
|
|
||||||
mHelpMessageLiveData.setValue(null);
|
|
||||||
mProgressLiveData.postValue(progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "onEnrollmentHelp(" + helpMsgId + ", " + helpString + ")");
|
|
||||||
}
|
|
||||||
mHelpMessageLiveData.postValue(new EnrollmentStatusMessage(helpMsgId, helpString));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnrollmentError(int errMsgId, CharSequence errString) {
|
|
||||||
Log.d(TAG, "onEnrollmentError(" + errMsgId + ", " + errString
|
|
||||||
+ "), cancelingQueueSize:" + mCancelingSignalQueue.size());
|
|
||||||
if (FINGERPRINT_ERROR_CANCELED == errMsgId && mCancelingSignalQueue.size() > 0) {
|
|
||||||
mCanceledSignalLiveData.postValue(mCancelingSignalQueue.poll());
|
|
||||||
} else {
|
|
||||||
mErrorMessageLiveData.postValue(new EnrollmentStatusMessage(errMsgId, errString));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAcquired(boolean isAcquiredGood) {
|
|
||||||
mAcquireLiveData.postValue(isAcquiredGood);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUdfpsPointerDown(int sensorId) {
|
|
||||||
mPointerDownLiveData.postValue(sensorId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUdfpsPointerUp(int sensorId) {
|
|
||||||
mPointerUpLiveData.postValue(sensorId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public FingerprintEnrollProgressViewModel(@NonNull Application application,
|
|
||||||
@NonNull FingerprintUpdater fingerprintUpdater, int userId) {
|
|
||||||
super(application);
|
|
||||||
mFingerprintUpdater = fingerprintUpdater;
|
|
||||||
mUserId = userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setToken(byte[] token) {
|
|
||||||
mToken = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* clear progress
|
|
||||||
*/
|
|
||||||
public void clearProgressLiveData() {
|
|
||||||
mProgressLiveData.setValue(new EnrollmentProgress(INITIAL_STEPS, INITIAL_REMAINING));
|
|
||||||
mHelpMessageLiveData.setValue(null);
|
|
||||||
mErrorMessageLiveData.setValue(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* clear error message
|
|
||||||
*/
|
|
||||||
public void clearErrorMessageLiveData() {
|
|
||||||
mErrorMessageLiveData.setValue(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<EnrollmentProgress> getProgressLiveData() {
|
|
||||||
return mProgressLiveData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<EnrollmentStatusMessage> getHelpMessageLiveData() {
|
|
||||||
return mHelpMessageLiveData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<EnrollmentStatusMessage> getErrorMessageLiveData() {
|
|
||||||
return mErrorMessageLiveData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Object> getCanceledSignalLiveData() {
|
|
||||||
return mCanceledSignalLiveData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Boolean> getAcquireLiveData() {
|
|
||||||
return mAcquireLiveData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Integer> getPointerDownLiveData() {
|
|
||||||
return mPointerDownLiveData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Integer> getPointerUpLiveData() {
|
|
||||||
return mPointerUpLiveData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts enrollment and return latest isEnrolling() result
|
|
||||||
*/
|
|
||||||
public Object startEnrollment(@EnrollReason int reason) {
|
|
||||||
if (mToken == null) {
|
|
||||||
Log.e(TAG, "Null hardware auth token for enroll");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (mCancellationSignal != null) {
|
|
||||||
Log.w(TAG, "Enrolling is running, shall not start again");
|
|
||||||
return mCancellationSignal;
|
|
||||||
}
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.e(TAG, "startEnrollment(" + reason + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear data
|
|
||||||
mProgressLiveData.setValue(new EnrollmentProgress(INITIAL_STEPS, INITIAL_REMAINING));
|
|
||||||
mHelpMessageLiveData.setValue(null);
|
|
||||||
mErrorMessageLiveData.setValue(null);
|
|
||||||
|
|
||||||
mCancellationSignal = new CancellationSignal();
|
|
||||||
|
|
||||||
final Resources res = getApplication().getResources();
|
|
||||||
if (reason == ENROLL_ENROLL
|
|
||||||
&& res.getBoolean(R.bool.enrollment_message_display_controller_flag)) {
|
|
||||||
final EnrollmentCallback callback = new MessageDisplayController(
|
|
||||||
getApplication().getMainThreadHandler(),
|
|
||||||
mEnrollmentCallback,
|
|
||||||
SystemClock.elapsedRealtimeClock(),
|
|
||||||
res.getInteger(R.integer.enrollment_help_minimum_time_display),
|
|
||||||
res.getInteger(R.integer.enrollment_progress_minimum_time_display),
|
|
||||||
res.getBoolean(R.bool.enrollment_progress_priority_over_help),
|
|
||||||
res.getBoolean(R.bool.enrollment_prioritize_acquire_messages),
|
|
||||||
res.getInteger(R.integer.enrollment_collect_time));
|
|
||||||
mFingerprintUpdater.enroll(mToken, mCancellationSignal, mUserId, callback, reason,
|
|
||||||
new Intent());
|
|
||||||
} else {
|
|
||||||
mFingerprintUpdater.enroll(mToken, mCancellationSignal, mUserId, mEnrollmentCallback,
|
|
||||||
reason, new Intent());
|
|
||||||
}
|
|
||||||
return mCancellationSignal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels enrollment and return latest isEnrolling result
|
|
||||||
*/
|
|
||||||
public boolean cancelEnrollment() {
|
|
||||||
final CancellationSignal cancellationSignal = mCancellationSignal;
|
|
||||||
mCancellationSignal = null;
|
|
||||||
|
|
||||||
if (cancellationSignal == null) {
|
|
||||||
Log.e(TAG, "Fail to cancel enrollment, has cancelled or not start");
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "enrollment cancelled");
|
|
||||||
}
|
|
||||||
mCancelingSignalQueue.add(cancellationSignal);
|
|
||||||
cancellationSignal.cancel();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEnrolling() {
|
|
||||||
return (mCancellationSignal != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getSteps() {
|
|
||||||
return mProgressLiveData.getValue().getSteps();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.result.ActivityResult
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import com.android.settings.biometrics.BiometricEnrollBase
|
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFinish.FINGERPRINT_SUGGESTION_ACTIVITY
|
|
||||||
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction
|
|
||||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentRequest
|
|
||||||
import kotlinx.atomicfu.AtomicBoolean
|
|
||||||
import kotlinx.atomicfu.atomic
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fingerprint enrollment view model implementation
|
|
||||||
*/
|
|
||||||
class FingerprintEnrollmentViewModel(
|
|
||||||
application: Application,
|
|
||||||
private val fingerprintRepository: FingerprintRepository,
|
|
||||||
val request: EnrollmentRequest
|
|
||||||
) : AndroidViewModel(application) {
|
|
||||||
|
|
||||||
val isWaitingActivityResult: AtomicBoolean = atomic(false)
|
|
||||||
|
|
||||||
private val _setResultFlow = MutableSharedFlow<ActivityResult>()
|
|
||||||
val setResultFlow: SharedFlow<ActivityResult>
|
|
||||||
get() = _setResultFlow.asSharedFlow()
|
|
||||||
|
|
||||||
var isNewFingerprintAdded = false
|
|
||||||
set(value) {
|
|
||||||
// Only allow changing this value from false to true
|
|
||||||
if (!field) {
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get override activity result as current ViewModel status.
|
|
||||||
*
|
|
||||||
* FingerprintEnrollmentActivity supports user enrolls 2nd fingerprint or starts a new flow
|
|
||||||
* through Deferred-SUW, Portal-SUW, or SUW Suggestion. Use a method to get override activity
|
|
||||||
* result instead of putting these if-else on every setResult(), .
|
|
||||||
*/
|
|
||||||
fun getOverrideActivityResult(
|
|
||||||
result: ActivityResult,
|
|
||||||
generatingChallengeExtras: Bundle?
|
|
||||||
): ActivityResult {
|
|
||||||
val newResultCode = if (isNewFingerprintAdded)
|
|
||||||
BiometricEnrollBase.RESULT_FINISHED
|
|
||||||
else if (request.isAfterSuwOrSuwSuggestedAction)
|
|
||||||
BiometricEnrollBase.RESULT_CANCELED
|
|
||||||
else
|
|
||||||
result.resultCode
|
|
||||||
|
|
||||||
var newData = result.data
|
|
||||||
if (newResultCode == BiometricEnrollBase.RESULT_FINISHED
|
|
||||||
&& generatingChallengeExtras != null
|
|
||||||
) {
|
|
||||||
if (newData == null) {
|
|
||||||
newData = Intent()
|
|
||||||
}
|
|
||||||
newData.putExtras(generatingChallengeExtras)
|
|
||||||
}
|
|
||||||
return ActivityResult(newResultCode, newData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
fun checkFinishActivityDuringOnPause(
|
|
||||||
isActivityFinishing: Boolean,
|
|
||||||
isChangingConfigurations: Boolean,
|
|
||||||
scope: CoroutineScope
|
|
||||||
) {
|
|
||||||
if (isChangingConfigurations || isActivityFinishing || request.isSuw
|
|
||||||
|| isWaitingActivityResult.value
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
scope.launch {
|
|
||||||
_setResultFlow.emit(ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Suw fingerprint count extra for statistics
|
|
||||||
*/
|
|
||||||
fun getSuwFingerprintCountExtra(userId: Int) = Bundle().also {
|
|
||||||
it.putInt(
|
|
||||||
SetupFingerprintEnrollIntroduction.EXTRA_FINGERPRINT_ENROLLED_COUNT,
|
|
||||||
fingerprintRepository.getNumOfEnrolledFingerprintsSize(userId)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the result about fingerprint enrollable
|
|
||||||
*/
|
|
||||||
fun isMaxEnrolledReached(userId: Int): Boolean = with(fingerprintRepository) {
|
|
||||||
maxFingerprints <= getNumOfEnrolledFingerprintsSize(userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
val canAssumeUdfps: Boolean
|
|
||||||
get() = fingerprintRepository.canAssumeUdfps()
|
|
||||||
|
|
||||||
val canAssumeSfps: Boolean
|
|
||||||
get() = fingerprintRepository.canAssumeSfps()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update FINGERPRINT_SUGGESTION_ACTIVITY into package manager
|
|
||||||
*/
|
|
||||||
fun updateFingerprintSuggestionEnableState(userId: Int) {
|
|
||||||
// Only show "Add another fingerprint" if the user already enrolled one.
|
|
||||||
// "Add fingerprint" will be shown in the main flow if the user hasn't enrolled any
|
|
||||||
// fingerprints. If the user already added more than one fingerprint, they already know
|
|
||||||
// to add multiple fingerprints so we don't show the suggestion.
|
|
||||||
val state = if (fingerprintRepository.getNumOfEnrolledFingerprintsSize(userId) == 1)
|
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
|
||||||
else
|
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
|
||||||
getApplication<Application>().packageManager.setComponentEnabledSetting(
|
|
||||||
ComponentName(
|
|
||||||
getApplication(),
|
|
||||||
FINGERPRINT_SUGGESTION_ACTIVITY
|
|
||||||
),
|
|
||||||
state,
|
|
||||||
PackageManager.DONT_KILL_APP
|
|
||||||
)
|
|
||||||
Log.d(TAG, "$FINGERPRINT_SUGGESTION_ACTIVITY enabled state: $state")
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "FingerprintEnrollmentViewModel"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,436 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.widget;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorSet;
|
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.ColorFilter;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.PointF;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.RectF;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.graphics.drawable.ShapeDrawable;
|
|
||||||
import android.graphics.drawable.shapes.PathShape;
|
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.UserHandle;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.PathParser;
|
|
||||||
import android.util.TypedValue;
|
|
||||||
import android.view.accessibility.AccessibilityManager;
|
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UDFPS fingerprint drawable that is shown when enrolling
|
|
||||||
*/
|
|
||||||
public class UdfpsEnrollDrawable extends Drawable {
|
|
||||||
private static final String TAG = "UdfpsAnimationEnroll";
|
|
||||||
|
|
||||||
private static final long TARGET_ANIM_DURATION_LONG = 800L;
|
|
||||||
private static final long TARGET_ANIM_DURATION_SHORT = 600L;
|
|
||||||
// 1 + SCALE_MAX is the maximum that the moving target will animate to
|
|
||||||
private static final float SCALE_MAX = 0.25f;
|
|
||||||
private static final float DEFAULT_STROKE_WIDTH = 3f;
|
|
||||||
private static final float SCALE = 0.5f;
|
|
||||||
private static final String SCALE_OVERRIDE =
|
|
||||||
"com.android.systemui.biometrics.UdfpsEnrollHelper.scale";
|
|
||||||
private static final String NEW_COORDS_OVERRIDE =
|
|
||||||
"com.android.systemui.biometrics.UdfpsNewCoords";
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private final Drawable mMovingTargetFpIcon;
|
|
||||||
@NonNull
|
|
||||||
private final Paint mSensorOutlinePaint;
|
|
||||||
@NonNull
|
|
||||||
private final Paint mBlueFill;
|
|
||||||
@NonNull
|
|
||||||
private final ShapeDrawable mFingerprintDrawable;
|
|
||||||
private int mAlpha;
|
|
||||||
private boolean mSkipDraw = false;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private RectF mSensorRect;
|
|
||||||
|
|
||||||
// Moving target animator set
|
|
||||||
@Nullable
|
|
||||||
AnimatorSet mTargetAnimatorSet;
|
|
||||||
// Moving target location
|
|
||||||
float mCurrentX;
|
|
||||||
float mCurrentY;
|
|
||||||
// Moving target size
|
|
||||||
float mCurrentScale = 1.f;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private final Animator.AnimatorListener mTargetAnimListener;
|
|
||||||
|
|
||||||
private boolean mShouldShowTipHint = false;
|
|
||||||
private boolean mShouldShowEdgeHint = false;
|
|
||||||
|
|
||||||
private int mEnrollIcon;
|
|
||||||
private int mMovingTargetFill;
|
|
||||||
|
|
||||||
private int mTotalSteps = -1;
|
|
||||||
private int mRemainingSteps = -1;
|
|
||||||
private int mLocationsEnrolled = 0;
|
|
||||||
private int mCenterTouchCount = 0;
|
|
||||||
|
|
||||||
private FingerprintManager mFingerprintManager;
|
|
||||||
|
|
||||||
private boolean mAccessibilityEnabled;
|
|
||||||
private Context mContext;
|
|
||||||
private final List<PointF> mGuidedEnrollmentPoints;
|
|
||||||
|
|
||||||
UdfpsEnrollDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
|
|
||||||
mFingerprintDrawable = defaultFactory(context);
|
|
||||||
|
|
||||||
loadResources(context, attrs);
|
|
||||||
mSensorOutlinePaint = new Paint(0 /* flags */);
|
|
||||||
mSensorOutlinePaint.setAntiAlias(true);
|
|
||||||
mSensorOutlinePaint.setColor(mMovingTargetFill);
|
|
||||||
mSensorOutlinePaint.setStyle(Paint.Style.FILL);
|
|
||||||
|
|
||||||
mBlueFill = new Paint(0 /* flags */);
|
|
||||||
mBlueFill.setAntiAlias(true);
|
|
||||||
mBlueFill.setColor(mMovingTargetFill);
|
|
||||||
mBlueFill.setStyle(Paint.Style.FILL);
|
|
||||||
|
|
||||||
mMovingTargetFpIcon = context.getResources()
|
|
||||||
.getDrawable(R.drawable.ic_enrollment_fingerprint, null);
|
|
||||||
mMovingTargetFpIcon.setTint(mEnrollIcon);
|
|
||||||
mMovingTargetFpIcon.mutate();
|
|
||||||
|
|
||||||
mFingerprintDrawable.setTint(mEnrollIcon);
|
|
||||||
|
|
||||||
setAlpha(255);
|
|
||||||
mTargetAnimListener = new Animator.AnimatorListener() {
|
|
||||||
@Override
|
|
||||||
public void onAnimationStart(Animator animation) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation) {
|
|
||||||
updateTipHintVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationCancel(Animator animation) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationRepeat(Animator animation) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mContext = context;
|
|
||||||
mFingerprintManager = context.getSystemService(FingerprintManager.class);
|
|
||||||
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
|
|
||||||
mAccessibilityEnabled = am.isEnabled();
|
|
||||||
mGuidedEnrollmentPoints = new ArrayList<>();
|
|
||||||
initEnrollPoint(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The [sensorRect] coordinates for the sensor area. */
|
|
||||||
void onSensorRectUpdated(@NonNull RectF sensorRect) {
|
|
||||||
int margin = ((int) sensorRect.height()) / 8;
|
|
||||||
Rect bounds = new Rect((int) (sensorRect.left) + margin, (int) (sensorRect.top) + margin,
|
|
||||||
(int) (sensorRect.right) - margin, (int) (sensorRect.bottom) - margin);
|
|
||||||
updateFingerprintIconBounds(bounds);
|
|
||||||
mSensorRect = sensorRect;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setShouldSkipDraw(boolean skipDraw) {
|
|
||||||
if (mSkipDraw == skipDraw) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mSkipDraw = skipDraw;
|
|
||||||
invalidateSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateFingerprintIconBounds(@NonNull Rect bounds) {
|
|
||||||
mFingerprintDrawable.setBounds(bounds);
|
|
||||||
invalidateSelf();
|
|
||||||
mMovingTargetFpIcon.setBounds(bounds);
|
|
||||||
invalidateSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
void onEnrollmentProgress(final int remaining, final int totalSteps) {
|
|
||||||
if (mTotalSteps == -1) {
|
|
||||||
mTotalSteps = totalSteps;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remaining != mRemainingSteps) {
|
|
||||||
mLocationsEnrolled++;
|
|
||||||
if (isCenterEnrollmentStage()) {
|
|
||||||
mCenterTouchCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mRemainingSteps = remaining;
|
|
||||||
|
|
||||||
if (!isCenterEnrollmentStage()) {
|
|
||||||
if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) {
|
|
||||||
mTargetAnimatorSet.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
final PointF point = getNextGuidedEnrollmentPoint();
|
|
||||||
if (mCurrentX != point.x || mCurrentY != point.y) {
|
|
||||||
final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
|
|
||||||
x.addUpdateListener(animation -> {
|
|
||||||
mCurrentX = (float) animation.getAnimatedValue();
|
|
||||||
invalidateSelf();
|
|
||||||
});
|
|
||||||
|
|
||||||
final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
|
|
||||||
y.addUpdateListener(animation -> {
|
|
||||||
mCurrentY = (float) animation.getAnimatedValue();
|
|
||||||
invalidateSelf();
|
|
||||||
});
|
|
||||||
|
|
||||||
final boolean isMovingToCenter = point.x == 0f && point.y == 0f;
|
|
||||||
final long duration = isMovingToCenter
|
|
||||||
? TARGET_ANIM_DURATION_SHORT
|
|
||||||
: TARGET_ANIM_DURATION_LONG;
|
|
||||||
|
|
||||||
final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
|
|
||||||
scale.setDuration(duration);
|
|
||||||
scale.addUpdateListener(animation -> {
|
|
||||||
// Grow then shrink
|
|
||||||
mCurrentScale = 1
|
|
||||||
+ SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
|
|
||||||
invalidateSelf();
|
|
||||||
});
|
|
||||||
|
|
||||||
mTargetAnimatorSet = new AnimatorSet();
|
|
||||||
|
|
||||||
mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
|
|
||||||
mTargetAnimatorSet.setDuration(duration);
|
|
||||||
mTargetAnimatorSet.addListener(mTargetAnimListener);
|
|
||||||
mTargetAnimatorSet.playTogether(x, y, scale);
|
|
||||||
mTargetAnimatorSet.start();
|
|
||||||
} else {
|
|
||||||
updateTipHintVisibility();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
updateTipHintVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateEdgeHintVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw(@NonNull Canvas canvas) {
|
|
||||||
if (mSkipDraw) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw moving target
|
|
||||||
if (!isCenterEnrollmentStage()) {
|
|
||||||
canvas.save();
|
|
||||||
canvas.translate(mCurrentX, mCurrentY);
|
|
||||||
|
|
||||||
if (mSensorRect != null) {
|
|
||||||
canvas.scale(mCurrentScale, mCurrentScale,
|
|
||||||
mSensorRect.centerX(), mSensorRect.centerY());
|
|
||||||
canvas.drawOval(mSensorRect, mBlueFill);
|
|
||||||
}
|
|
||||||
|
|
||||||
mMovingTargetFpIcon.draw(canvas);
|
|
||||||
canvas.restore();
|
|
||||||
} else {
|
|
||||||
if (mSensorRect != null) {
|
|
||||||
canvas.drawOval(mSensorRect, mSensorOutlinePaint);
|
|
||||||
}
|
|
||||||
mFingerprintDrawable.draw(canvas);
|
|
||||||
mFingerprintDrawable.setAlpha(getAlpha());
|
|
||||||
mSensorOutlinePaint.setAlpha(getAlpha());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setAlpha(int alpha) {
|
|
||||||
mAlpha = alpha;
|
|
||||||
mFingerprintDrawable.setAlpha(alpha);
|
|
||||||
mSensorOutlinePaint.setAlpha(alpha);
|
|
||||||
mBlueFill.setAlpha(alpha);
|
|
||||||
mMovingTargetFpIcon.setAlpha(alpha);
|
|
||||||
invalidateSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getAlpha() {
|
|
||||||
return mAlpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOpacity() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTipHintVisibility() {
|
|
||||||
final boolean shouldShow = isTipEnrollmentStage();
|
|
||||||
// With the new update, we will git rid of most of this code, and instead
|
|
||||||
// we will change the fingerprint icon.
|
|
||||||
if (mShouldShowTipHint == shouldShow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mShouldShowTipHint = shouldShow;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateEdgeHintVisibility() {
|
|
||||||
final boolean shouldShow = isEdgeEnrollmentStage();
|
|
||||||
if (mShouldShowEdgeHint == shouldShow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mShouldShowEdgeHint = shouldShow;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ShapeDrawable defaultFactory(Context context) {
|
|
||||||
String fpPath = context.getResources().getString(R.string.config_udfpsIcon);
|
|
||||||
ShapeDrawable drawable = new ShapeDrawable(
|
|
||||||
new PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)
|
|
||||||
);
|
|
||||||
drawable.mutate();
|
|
||||||
drawable.getPaint().setStyle(Paint.Style.STROKE);
|
|
||||||
drawable.getPaint().setStrokeCap(Paint.Cap.ROUND);
|
|
||||||
drawable.getPaint().setStrokeWidth(DEFAULT_STROKE_WIDTH);
|
|
||||||
return drawable;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadResources(Context context, @Nullable AttributeSet attrs) {
|
|
||||||
final TypedArray ta = context.obtainStyledAttributes(attrs,
|
|
||||||
R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
|
|
||||||
R.style.BiometricsEnrollStyle);
|
|
||||||
mEnrollIcon = ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollIcon, 0);
|
|
||||||
mMovingTargetFill = ta.getColor(
|
|
||||||
R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
|
|
||||||
ta.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isCenterEnrollmentStage() {
|
|
||||||
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getStageThresholdSteps(int totalSteps, int stageIndex) {
|
|
||||||
return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
private PointF getNextGuidedEnrollmentPoint() {
|
|
||||||
if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) {
|
|
||||||
return new PointF(0f, 0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
float scale = SCALE;
|
|
||||||
if (Build.IS_ENG || Build.IS_USERDEBUG) {
|
|
||||||
scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
|
|
||||||
SCALE_OVERRIDE, SCALE,
|
|
||||||
UserHandle.USER_CURRENT);
|
|
||||||
}
|
|
||||||
final int index = mLocationsEnrolled - mCenterTouchCount;
|
|
||||||
final PointF originalPoint = mGuidedEnrollmentPoints
|
|
||||||
.get(index % mGuidedEnrollmentPoints.size());
|
|
||||||
return new PointF(originalPoint.x * scale, originalPoint.y * scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isGuidedEnrollmentStage() {
|
|
||||||
if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final int progressSteps = mTotalSteps - mRemainingSteps;
|
|
||||||
return progressSteps >= getStageThresholdSteps(mTotalSteps, 0)
|
|
||||||
&& progressSteps < getStageThresholdSteps(mTotalSteps, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isTipEnrollmentStage() {
|
|
||||||
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final int progressSteps = mTotalSteps - mRemainingSteps;
|
|
||||||
return progressSteps >= getStageThresholdSteps(mTotalSteps, 1)
|
|
||||||
&& progressSteps < getStageThresholdSteps(mTotalSteps, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isEdgeEnrollmentStage() {
|
|
||||||
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initEnrollPoint(Context context) {
|
|
||||||
// Number of pixels per mm
|
|
||||||
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
|
|
||||||
context.getResources().getDisplayMetrics());
|
|
||||||
boolean useNewCoords = Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
|
||||||
NEW_COORDS_OVERRIDE, 0,
|
|
||||||
UserHandle.USER_CURRENT) != 0;
|
|
||||||
if (useNewCoords && (Build.IS_ENG || Build.IS_USERDEBUG)) {
|
|
||||||
Log.v(TAG, "Using new coordinates");
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, -1.02f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, 1.02f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(0.29f * px, 0.00f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(2.17f * px, -2.35f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(1.07f * px, -3.96f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, -4.31f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, -3.29f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, -1.23f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, 1.23f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, 3.29f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, 4.31f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(1.07f * px, 3.96f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(2.17f * px, 2.35f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(2.58f * px, 0.00f * px));
|
|
||||||
} else {
|
|
||||||
Log.v(TAG, "Using old coordinates");
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(2.00f * px, 0.00f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(0.87f * px, -2.70f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, 1.31f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(0.88f * px, 2.70f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(3.94f * px, -1.06f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(2.90f * px, -4.14f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-3.62f * px, 2.54f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(-1.49f * px, 5.57f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(2.29f * px, 4.92f * px));
|
|
||||||
mGuidedEnrollmentPoints.add(new PointF(3.82f * px, 1.78f * px));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,422 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.widget;
|
|
||||||
|
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.ColorFilter;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Process;
|
|
||||||
import android.os.VibrationAttributes;
|
|
||||||
import android.os.VibrationEffect;
|
|
||||||
import android.os.Vibrator;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
import android.view.accessibility.AccessibilityManager;
|
|
||||||
import android.view.animation.DecelerateInterpolator;
|
|
||||||
import android.view.animation.Interpolator;
|
|
||||||
import android.view.animation.OvershootInterpolator;
|
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
|
||||||
import com.android.settings.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UDFPS enrollment progress bar.
|
|
||||||
*/
|
|
||||||
public class UdfpsEnrollProgressBarDrawable extends Drawable {
|
|
||||||
private static final String TAG = "UdfpsProgressBar";
|
|
||||||
|
|
||||||
private static final long CHECKMARK_ANIMATION_DELAY_MS = 200L;
|
|
||||||
private static final long CHECKMARK_ANIMATION_DURATION_MS = 300L;
|
|
||||||
private static final long FILL_COLOR_ANIMATION_DURATION_MS = 350L;
|
|
||||||
private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
|
|
||||||
private static final float STROKE_WIDTH_DP = 12f;
|
|
||||||
private static final Interpolator DEACCEL = new DecelerateInterpolator();
|
|
||||||
|
|
||||||
private static final VibrationEffect VIBRATE_EFFECT_ERROR =
|
|
||||||
VibrationEffect.createWaveform(new long[]{0, 5, 55, 60}, -1);
|
|
||||||
private static final VibrationAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES =
|
|
||||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY);
|
|
||||||
|
|
||||||
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
|
|
||||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
|
|
||||||
|
|
||||||
private static final VibrationEffect SUCCESS_VIBRATION_EFFECT =
|
|
||||||
VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
|
|
||||||
|
|
||||||
private final float mStrokeWidthPx;
|
|
||||||
@ColorInt
|
|
||||||
private final int mProgressColor;
|
|
||||||
@ColorInt
|
|
||||||
private final int mHelpColor;
|
|
||||||
@ColorInt
|
|
||||||
private final int mOnFirstBucketFailedColor;
|
|
||||||
@NonNull
|
|
||||||
private final Drawable mCheckmarkDrawable;
|
|
||||||
@NonNull
|
|
||||||
private final Interpolator mCheckmarkInterpolator;
|
|
||||||
@NonNull
|
|
||||||
private final Paint mBackgroundPaint;
|
|
||||||
@VisibleForTesting
|
|
||||||
@NonNull
|
|
||||||
final Paint mFillPaint;
|
|
||||||
@NonNull
|
|
||||||
private final Vibrator mVibrator;
|
|
||||||
@NonNull
|
|
||||||
private final boolean mIsAccessibilityEnabled;
|
|
||||||
@NonNull
|
|
||||||
private final Context mContext;
|
|
||||||
|
|
||||||
private boolean mAfterFirstTouch;
|
|
||||||
|
|
||||||
private int mRemainingSteps = 0;
|
|
||||||
private int mTotalSteps = 0;
|
|
||||||
private float mProgress = 0f;
|
|
||||||
@Nullable
|
|
||||||
private ValueAnimator mProgressAnimator;
|
|
||||||
@NonNull
|
|
||||||
private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
|
|
||||||
|
|
||||||
private boolean mShowingHelp = false;
|
|
||||||
@Nullable
|
|
||||||
private ValueAnimator mFillColorAnimator;
|
|
||||||
@NonNull
|
|
||||||
private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private ValueAnimator mBackgroundColorAnimator;
|
|
||||||
@NonNull
|
|
||||||
private final ValueAnimator.AnimatorUpdateListener mBackgroundColorUpdateListener;
|
|
||||||
|
|
||||||
private boolean mComplete = false;
|
|
||||||
private float mCheckmarkScale = 0f;
|
|
||||||
@Nullable
|
|
||||||
private ValueAnimator mCheckmarkAnimator;
|
|
||||||
@NonNull
|
|
||||||
private final ValueAnimator.AnimatorUpdateListener mCheckmarkUpdateListener;
|
|
||||||
|
|
||||||
private int mMovingTargetFill;
|
|
||||||
private int mMovingTargetFillError;
|
|
||||||
private int mEnrollProgress;
|
|
||||||
private int mEnrollProgressHelp;
|
|
||||||
private int mEnrollProgressHelpWithTalkback;
|
|
||||||
|
|
||||||
public UdfpsEnrollProgressBarDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
|
|
||||||
mContext = context;
|
|
||||||
|
|
||||||
loadResources(context, attrs);
|
|
||||||
float density = context.getResources().getDisplayMetrics().densityDpi;
|
|
||||||
mStrokeWidthPx = STROKE_WIDTH_DP * (density / DisplayMetrics.DENSITY_DEFAULT);
|
|
||||||
mProgressColor = mEnrollProgress;
|
|
||||||
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
|
|
||||||
mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
|
|
||||||
mOnFirstBucketFailedColor = mMovingTargetFillError;
|
|
||||||
if (!mIsAccessibilityEnabled) {
|
|
||||||
mHelpColor = mEnrollProgressHelp;
|
|
||||||
} else {
|
|
||||||
mHelpColor = mEnrollProgressHelpWithTalkback;
|
|
||||||
}
|
|
||||||
mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
|
|
||||||
mCheckmarkDrawable.mutate();
|
|
||||||
mCheckmarkInterpolator = new OvershootInterpolator();
|
|
||||||
|
|
||||||
mBackgroundPaint = new Paint();
|
|
||||||
mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
|
|
||||||
mBackgroundPaint.setColor(mMovingTargetFill);
|
|
||||||
mBackgroundPaint.setAntiAlias(true);
|
|
||||||
mBackgroundPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
|
|
||||||
|
|
||||||
// Progress fill should *not* use the extracted system color.
|
|
||||||
mFillPaint = new Paint();
|
|
||||||
mFillPaint.setStrokeWidth(mStrokeWidthPx);
|
|
||||||
mFillPaint.setColor(mProgressColor);
|
|
||||||
mFillPaint.setAntiAlias(true);
|
|
||||||
mFillPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
mFillPaint.setStrokeCap(Paint.Cap.ROUND);
|
|
||||||
|
|
||||||
mVibrator = mContext.getSystemService(Vibrator.class);
|
|
||||||
|
|
||||||
mProgressUpdateListener = animation -> {
|
|
||||||
mProgress = (float) animation.getAnimatedValue();
|
|
||||||
invalidateSelf();
|
|
||||||
};
|
|
||||||
|
|
||||||
mFillColorUpdateListener = animation -> {
|
|
||||||
mFillPaint.setColor((int) animation.getAnimatedValue());
|
|
||||||
invalidateSelf();
|
|
||||||
};
|
|
||||||
|
|
||||||
mCheckmarkUpdateListener = animation -> {
|
|
||||||
mCheckmarkScale = (float) animation.getAnimatedValue();
|
|
||||||
invalidateSelf();
|
|
||||||
};
|
|
||||||
|
|
||||||
mBackgroundColorUpdateListener = animation -> {
|
|
||||||
mBackgroundPaint.setColor((int) animation.getAnimatedValue());
|
|
||||||
invalidateSelf();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void onEnrollmentProgress(final int remaining, final int totalSteps) {
|
|
||||||
mAfterFirstTouch = true;
|
|
||||||
updateState(remaining, totalSteps, false /* showingHelp */);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onEnrollmentHelp(int remaining, int totalSteps) {
|
|
||||||
updateState(remaining, totalSteps, true /* showingHelp */);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onLastStepAcquired() {
|
|
||||||
updateState(0, mTotalSteps, false /* showingHelp */);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateState(int remainingSteps, int totalSteps, boolean showingHelp) {
|
|
||||||
updateProgress(remainingSteps, totalSteps, showingHelp);
|
|
||||||
updateFillColor(showingHelp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateProgress(int remainingSteps, int totalSteps, boolean showingHelp) {
|
|
||||||
if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mShowingHelp) {
|
|
||||||
if (mVibrator != null && mIsAccessibilityEnabled) {
|
|
||||||
mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
|
|
||||||
VIBRATE_EFFECT_ERROR, getClass().getSimpleName() + "::onEnrollmentHelp",
|
|
||||||
FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If the first touch is an error, remainingSteps will be -1 and the callback
|
|
||||||
// doesn't come from onEnrollmentHelp. If we are in the accessibility flow,
|
|
||||||
// we still would like to vibrate.
|
|
||||||
if (mVibrator != null) {
|
|
||||||
if (remainingSteps == -1 && mIsAccessibilityEnabled) {
|
|
||||||
mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
|
|
||||||
VIBRATE_EFFECT_ERROR,
|
|
||||||
getClass().getSimpleName() + "::onFirstTouchError",
|
|
||||||
FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
|
|
||||||
} else if (remainingSteps != -1 && !mIsAccessibilityEnabled) {
|
|
||||||
mVibrator.vibrate(Process.myUid(),
|
|
||||||
mContext.getOpPackageName(),
|
|
||||||
SUCCESS_VIBRATION_EFFECT,
|
|
||||||
getClass().getSimpleName() + "::OnEnrollmentProgress",
|
|
||||||
HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mShowingHelp = showingHelp;
|
|
||||||
mRemainingSteps = remainingSteps;
|
|
||||||
mTotalSteps = totalSteps;
|
|
||||||
|
|
||||||
final int progressSteps = Math.max(0, totalSteps - remainingSteps);
|
|
||||||
|
|
||||||
// If needed, add 1 to progress and total steps to account for initial touch.
|
|
||||||
final int adjustedSteps = mAfterFirstTouch ? progressSteps + 1 : progressSteps;
|
|
||||||
final int adjustedTotal = mAfterFirstTouch ? mTotalSteps + 1 : mTotalSteps;
|
|
||||||
|
|
||||||
final float targetProgress = Math.min(1f, (float) adjustedSteps / (float) adjustedTotal);
|
|
||||||
|
|
||||||
if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
|
|
||||||
mProgressAnimator.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
mProgressAnimator = ValueAnimator.ofFloat(mProgress, targetProgress);
|
|
||||||
mProgressAnimator.setDuration(PROGRESS_ANIMATION_DURATION_MS);
|
|
||||||
mProgressAnimator.addUpdateListener(mProgressUpdateListener);
|
|
||||||
mProgressAnimator.start();
|
|
||||||
|
|
||||||
if (remainingSteps == 0) {
|
|
||||||
startCompletionAnimation();
|
|
||||||
} else if (remainingSteps > 0) {
|
|
||||||
rollBackCompletionAnimation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void animateBackgroundColor() {
|
|
||||||
if (mBackgroundColorAnimator != null && mBackgroundColorAnimator.isRunning()) {
|
|
||||||
mBackgroundColorAnimator.end();
|
|
||||||
}
|
|
||||||
mBackgroundColorAnimator = ValueAnimator.ofArgb(mBackgroundPaint.getColor(),
|
|
||||||
mOnFirstBucketFailedColor);
|
|
||||||
mBackgroundColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
|
|
||||||
mBackgroundColorAnimator.setRepeatCount(1);
|
|
||||||
mBackgroundColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
|
|
||||||
mBackgroundColorAnimator.setInterpolator(DEACCEL);
|
|
||||||
mBackgroundColorAnimator.addUpdateListener(mBackgroundColorUpdateListener);
|
|
||||||
mBackgroundColorAnimator.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateFillColor(boolean showingHelp) {
|
|
||||||
if (!mAfterFirstTouch && showingHelp) {
|
|
||||||
// If we are on the first touch, animate the background color
|
|
||||||
// instead of the progress color.
|
|
||||||
animateBackgroundColor();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
|
|
||||||
mFillColorAnimator.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
@ColorInt final int targetColor = showingHelp ? mHelpColor : mProgressColor;
|
|
||||||
mFillColorAnimator = ValueAnimator.ofArgb(mFillPaint.getColor(), targetColor);
|
|
||||||
mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
|
|
||||||
mFillColorAnimator.setRepeatCount(1);
|
|
||||||
mFillColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
|
|
||||||
mFillColorAnimator.setInterpolator(DEACCEL);
|
|
||||||
mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
|
|
||||||
mFillColorAnimator.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startCompletionAnimation() {
|
|
||||||
if (mComplete) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mComplete = true;
|
|
||||||
|
|
||||||
if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
|
|
||||||
mCheckmarkAnimator.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 1f);
|
|
||||||
mCheckmarkAnimator.setStartDelay(CHECKMARK_ANIMATION_DELAY_MS);
|
|
||||||
mCheckmarkAnimator.setDuration(CHECKMARK_ANIMATION_DURATION_MS);
|
|
||||||
mCheckmarkAnimator.setInterpolator(mCheckmarkInterpolator);
|
|
||||||
mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
|
|
||||||
mCheckmarkAnimator.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rollBackCompletionAnimation() {
|
|
||||||
if (!mComplete) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mComplete = false;
|
|
||||||
|
|
||||||
// Adjust duration based on how much of the completion animation has played.
|
|
||||||
final float animatedFraction = mCheckmarkAnimator != null
|
|
||||||
? mCheckmarkAnimator.getAnimatedFraction()
|
|
||||||
: 0f;
|
|
||||||
final long durationMs = Math.round(CHECKMARK_ANIMATION_DELAY_MS * animatedFraction);
|
|
||||||
|
|
||||||
if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
|
|
||||||
mCheckmarkAnimator.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 0f);
|
|
||||||
mCheckmarkAnimator.setDuration(durationMs);
|
|
||||||
mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
|
|
||||||
mCheckmarkAnimator.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadResources(Context context, @Nullable AttributeSet attrs) {
|
|
||||||
final TypedArray ta = context.obtainStyledAttributes(attrs,
|
|
||||||
R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
|
|
||||||
R.style.BiometricsEnrollStyle);
|
|
||||||
mMovingTargetFill = ta.getColor(
|
|
||||||
R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
|
|
||||||
mMovingTargetFillError = ta.getColor(
|
|
||||||
R.styleable.BiometricsEnrollView_biometricsMovingTargetFillError, 0);
|
|
||||||
mEnrollProgress = ta.getColor(
|
|
||||||
R.styleable.BiometricsEnrollView_biometricsEnrollProgress, 0);
|
|
||||||
mEnrollProgressHelp = ta.getColor(
|
|
||||||
R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelp, 0);
|
|
||||||
mEnrollProgressHelpWithTalkback = ta.getColor(
|
|
||||||
R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelpWithTalkback, 0);
|
|
||||||
ta.recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw(@NonNull Canvas canvas) {
|
|
||||||
canvas.save();
|
|
||||||
|
|
||||||
// Progress starts from the top, instead of the right
|
|
||||||
canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
|
|
||||||
|
|
||||||
final float halfPaddingPx = mStrokeWidthPx / 2f;
|
|
||||||
|
|
||||||
if (mProgress < 1f) {
|
|
||||||
// Draw the background color of the progress circle.
|
|
||||||
canvas.drawArc(
|
|
||||||
halfPaddingPx,
|
|
||||||
halfPaddingPx,
|
|
||||||
getBounds().right - halfPaddingPx,
|
|
||||||
getBounds().bottom - halfPaddingPx,
|
|
||||||
0f /* startAngle */,
|
|
||||||
360f /* sweepAngle */,
|
|
||||||
false /* useCenter */,
|
|
||||||
mBackgroundPaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mProgress > 0f) {
|
|
||||||
// Draw the filled portion of the progress circle.
|
|
||||||
canvas.drawArc(
|
|
||||||
halfPaddingPx,
|
|
||||||
halfPaddingPx,
|
|
||||||
getBounds().right - halfPaddingPx,
|
|
||||||
getBounds().bottom - halfPaddingPx,
|
|
||||||
0f /* startAngle */,
|
|
||||||
360f * mProgress /* sweepAngle */,
|
|
||||||
false /* useCenter */,
|
|
||||||
mFillPaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.restore();
|
|
||||||
|
|
||||||
if (mCheckmarkScale > 0f) {
|
|
||||||
final float offsetScale = (float) Math.sqrt(2) / 2f;
|
|
||||||
final float centerXOffset = (getBounds().width() - mStrokeWidthPx) / 2f * offsetScale;
|
|
||||||
final float centerYOffset = (getBounds().height() - mStrokeWidthPx) / 2f * offsetScale;
|
|
||||||
final float centerX = getBounds().centerX() + centerXOffset;
|
|
||||||
final float centerY = getBounds().centerY() + centerYOffset;
|
|
||||||
|
|
||||||
final float boundsXOffset =
|
|
||||||
mCheckmarkDrawable.getIntrinsicWidth() / 2f * mCheckmarkScale;
|
|
||||||
final float boundsYOffset =
|
|
||||||
mCheckmarkDrawable.getIntrinsicHeight() / 2f * mCheckmarkScale;
|
|
||||||
|
|
||||||
final int left = Math.round(centerX - boundsXOffset);
|
|
||||||
final int top = Math.round(centerY - boundsYOffset);
|
|
||||||
final int right = Math.round(centerX + boundsXOffset);
|
|
||||||
final int bottom = Math.round(centerY + boundsYOffset);
|
|
||||||
mCheckmarkDrawable.setBounds(left, top, right, bottom);
|
|
||||||
mCheckmarkDrawable.draw(canvas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setAlpha(int alpha) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOpacity() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,277 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.widget;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.RectF;
|
|
||||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.RotationUtils;
|
|
||||||
import android.view.DisplayInfo;
|
|
||||||
import android.view.Surface;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.RelativeLayout;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.systemui.biometrics.UdfpsUtils;
|
|
||||||
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View corresponding with udfps_enroll_view.xml
|
|
||||||
*/
|
|
||||||
public class UdfpsEnrollView extends FrameLayout {
|
|
||||||
private static final String TAG = "UdfpsEnrollView";
|
|
||||||
@NonNull
|
|
||||||
private final UdfpsEnrollDrawable mFingerprintDrawable;
|
|
||||||
@NonNull
|
|
||||||
private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable;
|
|
||||||
@NonNull
|
|
||||||
private final Handler mHandler;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private ImageView mFingerprintProgressView;
|
|
||||||
private UdfpsUtils mUdfpsUtils;
|
|
||||||
|
|
||||||
private int mProgressBarRadius;
|
|
||||||
|
|
||||||
private Rect mSensorRect;
|
|
||||||
private UdfpsOverlayParams mOverlayParams;
|
|
||||||
private FingerprintSensorPropertiesInternal mSensorProperties;
|
|
||||||
|
|
||||||
private int mTotalSteps = -1;
|
|
||||||
private int mRemainingSteps = -1;
|
|
||||||
private int mLocationsEnrolled = 0;
|
|
||||||
private int mCenterTouchCount = 0;
|
|
||||||
|
|
||||||
public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
mFingerprintDrawable = new UdfpsEnrollDrawable(mContext, attrs);
|
|
||||||
mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, attrs);
|
|
||||||
mHandler = new Handler(Looper.getMainLooper());
|
|
||||||
mUdfpsUtils = new UdfpsUtils();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onFinishInflate() {
|
|
||||||
ImageView fingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view);
|
|
||||||
fingerprintView.setImageDrawable(mFingerprintDrawable);
|
|
||||||
mFingerprintProgressView = findViewById(R.id.udfps_enroll_animation_fp_progress_view);
|
|
||||||
mFingerprintProgressView.setImageDrawable(mFingerprintProgressDrawable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receive enroll progress information from FingerprintEnrollEnrollingUdfpsFragment
|
|
||||||
*/
|
|
||||||
public void onEnrollmentProgress(int remaining, int totalSteps) {
|
|
||||||
if (mTotalSteps == -1) {
|
|
||||||
mTotalSteps = totalSteps;
|
|
||||||
}
|
|
||||||
mRemainingSteps = remaining;
|
|
||||||
mHandler.post(() -> {
|
|
||||||
mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps);
|
|
||||||
mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receive enroll help information from FingerprintEnrollEnrollingUdfpsFragment
|
|
||||||
*/
|
|
||||||
public void onEnrollmentHelp() {
|
|
||||||
mHandler.post(
|
|
||||||
() -> mFingerprintProgressDrawable.onEnrollmentHelp(mRemainingSteps, mTotalSteps));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receive onAcquired from FingerprintEnrollEnrollingUdfpsFragment
|
|
||||||
*/
|
|
||||||
public void onAcquired(boolean isAcquiredGood) {
|
|
||||||
final boolean animateIfLastStepGood =
|
|
||||||
isAcquiredGood && (mRemainingSteps <= 2 && mRemainingSteps >= 0);
|
|
||||||
mHandler.post(() -> {
|
|
||||||
onFingerUp();
|
|
||||||
if (animateIfLastStepGood) mFingerprintProgressDrawable.onLastStepAcquired();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receive onPointerDown from FingerprintEnrollEnrollingUdfpsFragment
|
|
||||||
*/
|
|
||||||
public void onPointerDown(int sensorId) {
|
|
||||||
onFingerDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receive onPointerUp from FingerprintEnrollEnrollingUdfpsFragment
|
|
||||||
*/
|
|
||||||
public void onPointerUp(int sensorId) {
|
|
||||||
onFingerUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ViewTreeObserver.OnDrawListener mOnDrawListener = this::updateOverlayParams;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* setup SensorProperties
|
|
||||||
*/
|
|
||||||
public void setSensorProperties(FingerprintSensorPropertiesInternal properties) {
|
|
||||||
mSensorProperties = properties;
|
|
||||||
((ViewGroup) getParent()).getViewTreeObserver().addOnDrawListener(mOnDrawListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDetachedFromWindow() {
|
|
||||||
final ViewGroup parent = (ViewGroup) getParent();
|
|
||||||
if (parent != null) {
|
|
||||||
final ViewTreeObserver observer = parent.getViewTreeObserver();
|
|
||||||
if (observer != null) {
|
|
||||||
observer.removeOnDrawListener(mOnDrawListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.onDetachedFromWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onSensorRectUpdated() {
|
|
||||||
updateDimensions();
|
|
||||||
|
|
||||||
// Updates sensor rect in relation to the overlay view
|
|
||||||
mSensorRect.set(getPaddingX(), getPaddingY(),
|
|
||||||
(mOverlayParams.getSensorBounds().width() + getPaddingX()),
|
|
||||||
(mOverlayParams.getSensorBounds().height() + getPaddingY()));
|
|
||||||
mFingerprintDrawable.onSensorRectUpdated(new RectF(mSensorRect));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateDimensions() {
|
|
||||||
// Original sensorBounds assume portrait mode.
|
|
||||||
final Rect rotatedBounds = new Rect(mOverlayParams.getSensorBounds());
|
|
||||||
int rotation = mOverlayParams.getRotation();
|
|
||||||
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
|
|
||||||
RotationUtils.rotateBounds(
|
|
||||||
rotatedBounds,
|
|
||||||
mOverlayParams.getNaturalDisplayWidth(),
|
|
||||||
mOverlayParams.getNaturalDisplayHeight(),
|
|
||||||
rotation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
RelativeLayout parent = ((RelativeLayout) getParent());
|
|
||||||
if (parent == null) {
|
|
||||||
Log.e(TAG, "Fail to updateDimensions for " + this + ", parent null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final int[] coords = parent.getLocationOnScreen();
|
|
||||||
final int parentLeft = coords[0];
|
|
||||||
final int parentTop = coords[1];
|
|
||||||
final int parentRight = parentLeft + parent.getWidth();
|
|
||||||
final int parentBottom = parentTop + parent.getHeight();
|
|
||||||
|
|
||||||
|
|
||||||
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(getWidth(),
|
|
||||||
getHeight());
|
|
||||||
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
|
|
||||||
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
|
||||||
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
|
|
||||||
params.rightMargin = parentRight - rotatedBounds.right - getPaddingX();
|
|
||||||
params.topMargin = rotatedBounds.top - parentTop - getPaddingY();
|
|
||||||
} else {
|
|
||||||
if (rotation == Surface.ROTATION_90) {
|
|
||||||
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
|
|
||||||
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
|
|
||||||
params.rightMargin = parentRight - rotatedBounds.right - getPaddingX();
|
|
||||||
params.bottomMargin = parentBottom - rotatedBounds.bottom - getPaddingY();
|
|
||||||
} else {
|
|
||||||
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
|
|
||||||
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
|
|
||||||
params.bottomMargin = parentBottom - rotatedBounds.bottom - getPaddingY();
|
|
||||||
params.leftMargin = rotatedBounds.left - parentLeft - getPaddingX();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
params.height = rotatedBounds.height() + 2 * getPaddingX();
|
|
||||||
params.width = rotatedBounds.width() + 2 * getPaddingY();
|
|
||||||
setLayoutParams(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onFingerDown() {
|
|
||||||
mFingerprintDrawable.setShouldSkipDraw(true);
|
|
||||||
mFingerprintDrawable.invalidateSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onFingerUp() {
|
|
||||||
mFingerprintDrawable.setShouldSkipDraw(false);
|
|
||||||
mFingerprintDrawable.invalidateSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getPaddingX() {
|
|
||||||
return mProgressBarRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getPaddingY() {
|
|
||||||
return mProgressBarRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateOverlayParams() {
|
|
||||||
|
|
||||||
if (mSensorProperties == null) {
|
|
||||||
android.util.Log.e(TAG, "There is no sensor info!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayInfo displayInfo = new DisplayInfo();
|
|
||||||
if (getDisplay() == null) {
|
|
||||||
android.util.Log.e(TAG, "Can not get display");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
getDisplay().getDisplayInfo(displayInfo);
|
|
||||||
Rect udfpsBounds = mSensorProperties.getLocation().getRect();
|
|
||||||
float scaleFactor = mUdfpsUtils.getScaleFactor(displayInfo);
|
|
||||||
udfpsBounds.scale(scaleFactor);
|
|
||||||
|
|
||||||
final Rect overlayBounds = new Rect(
|
|
||||||
0, /* left */
|
|
||||||
displayInfo.getNaturalHeight() / 2, /* top */
|
|
||||||
displayInfo.getNaturalWidth(), /* right */
|
|
||||||
displayInfo.getNaturalHeight() /* botom */);
|
|
||||||
|
|
||||||
mOverlayParams = new UdfpsOverlayParams(
|
|
||||||
udfpsBounds,
|
|
||||||
overlayBounds,
|
|
||||||
displayInfo.getNaturalWidth(),
|
|
||||||
displayInfo.getNaturalHeight(),
|
|
||||||
scaleFactor,
|
|
||||||
displayInfo.rotation,
|
|
||||||
mSensorProperties.sensorType);
|
|
||||||
|
|
||||||
post(() -> {
|
|
||||||
mProgressBarRadius =
|
|
||||||
(int) (mOverlayParams.getScaleFactor() * getContext().getResources().getInteger(
|
|
||||||
R.integer.config_udfpsEnrollProgressBar));
|
|
||||||
mSensorRect = new Rect(mOverlayParams.getSensorBounds());
|
|
||||||
|
|
||||||
onSensorRectUpdated();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -22,7 +22,6 @@ import com.android.settings.accounts.AccountFeatureProvider
|
|||||||
import com.android.settings.applications.ApplicationFeatureProvider
|
import com.android.settings.applications.ApplicationFeatureProvider
|
||||||
import com.android.settings.biometrics.face.FaceFeatureProvider
|
import com.android.settings.biometrics.face.FaceFeatureProvider
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
|
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
|
||||||
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider
|
|
||||||
import com.android.settings.bluetooth.BluetoothFeatureProvider
|
import com.android.settings.bluetooth.BluetoothFeatureProvider
|
||||||
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider
|
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider
|
||||||
import com.android.settings.connecteddevice.stylus.StylusFeatureProvider
|
import com.android.settings.connecteddevice.stylus.StylusFeatureProvider
|
||||||
@@ -118,11 +117,6 @@ abstract class FeatureFactory {
|
|||||||
*/
|
*/
|
||||||
abstract val fingerprintFeatureProvider: FingerprintFeatureProvider
|
abstract val fingerprintFeatureProvider: FingerprintFeatureProvider
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets implementation for Biometrics repository provider.
|
|
||||||
*/
|
|
||||||
abstract val biometricsRepositoryProvider: BiometricsRepositoryProvider
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets implementation for the WifiTrackerLib.
|
* Gets implementation for the WifiTrackerLib.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import com.android.settings.biometrics.face.FaceFeatureProvider
|
|||||||
import com.android.settings.biometrics.face.FaceFeatureProviderImpl
|
import com.android.settings.biometrics.face.FaceFeatureProviderImpl
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
|
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProviderImpl
|
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProviderImpl
|
||||||
import com.android.settings.biometrics2.factory.BiometricsRepositoryProviderImpl
|
|
||||||
import com.android.settings.bluetooth.BluetoothFeatureProvider
|
import com.android.settings.bluetooth.BluetoothFeatureProvider
|
||||||
import com.android.settings.bluetooth.BluetoothFeatureProviderImpl
|
import com.android.settings.bluetooth.BluetoothFeatureProviderImpl
|
||||||
import com.android.settings.connecteddevice.dock.DockUpdaterFeatureProviderImpl
|
import com.android.settings.connecteddevice.dock.DockUpdaterFeatureProviderImpl
|
||||||
@@ -152,8 +151,6 @@ open class FeatureFactoryImpl : FeatureFactory() {
|
|||||||
FingerprintFeatureProviderImpl()
|
FingerprintFeatureProviderImpl()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val biometricsRepositoryProvider by lazy { BiometricsRepositoryProviderImpl() }
|
|
||||||
|
|
||||||
override val wifiTrackerLibProvider: WifiTrackerLibProvider by lazy {
|
override val wifiTrackerLibProvider: WifiTrackerLibProvider by lazy {
|
||||||
WifiTrackerLibProviderImpl()
|
WifiTrackerLibProviderImpl()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import com.android.settings.accounts.AccountFeatureProvider;
|
|||||||
import com.android.settings.applications.ApplicationFeatureProvider;
|
import com.android.settings.applications.ApplicationFeatureProvider;
|
||||||
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider;
|
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider;
|
||||||
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
|
|
||||||
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
||||||
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider;
|
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider;
|
||||||
import com.android.settings.connecteddevice.stylus.StylusFeatureProvider;
|
import com.android.settings.connecteddevice.stylus.StylusFeatureProvider;
|
||||||
@@ -83,7 +82,6 @@ public class FakeFeatureFactory extends FeatureFactory {
|
|||||||
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
|
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
|
||||||
public final FaceFeatureProvider mFaceFeatureProvider;
|
public final FaceFeatureProvider mFaceFeatureProvider;
|
||||||
public final FingerprintFeatureProvider mFingerprintFeatureProvider;
|
public final FingerprintFeatureProvider mFingerprintFeatureProvider;
|
||||||
public final BiometricsRepositoryProvider mBiometricsRepositoryProvider;
|
|
||||||
|
|
||||||
public PanelFeatureProvider panelFeatureProvider;
|
public PanelFeatureProvider panelFeatureProvider;
|
||||||
public SlicesFeatureProvider slicesFeatureProvider;
|
public SlicesFeatureProvider slicesFeatureProvider;
|
||||||
@@ -139,7 +137,6 @@ public class FakeFeatureFactory extends FeatureFactory {
|
|||||||
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
|
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
|
||||||
mFaceFeatureProvider = mock(FaceFeatureProvider.class);
|
mFaceFeatureProvider = mock(FaceFeatureProvider.class);
|
||||||
mFingerprintFeatureProvider = mock(FingerprintFeatureProvider.class);
|
mFingerprintFeatureProvider = mock(FingerprintFeatureProvider.class);
|
||||||
mBiometricsRepositoryProvider = mock(BiometricsRepositoryProvider.class);
|
|
||||||
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
|
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
|
||||||
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
|
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
|
||||||
mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
|
mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
|
||||||
@@ -271,11 +268,6 @@ public class FakeFeatureFactory extends FeatureFactory {
|
|||||||
return mFingerprintFeatureProvider;
|
return mFingerprintFeatureProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public BiometricsRepositoryProvider getBiometricsRepositoryProvider() {
|
|
||||||
return mBiometricsRepositoryProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WifiTrackerLibProvider getWifiTrackerLibProvider() {
|
public WifiTrackerLibProvider getWifiTrackerLibProvider() {
|
||||||
return wifiTrackerLibProvider;
|
return wifiTrackerLibProvider;
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import com.android.settings.accounts.AccountFeatureProvider
|
|||||||
import com.android.settings.applications.ApplicationFeatureProvider
|
import com.android.settings.applications.ApplicationFeatureProvider
|
||||||
import com.android.settings.biometrics.face.FaceFeatureProvider
|
import com.android.settings.biometrics.face.FaceFeatureProvider
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
|
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
|
||||||
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider
|
|
||||||
import com.android.settings.bluetooth.BluetoothFeatureProvider
|
import com.android.settings.bluetooth.BluetoothFeatureProvider
|
||||||
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider
|
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider
|
||||||
import com.android.settings.connecteddevice.stylus.StylusFeatureProvider
|
import com.android.settings.connecteddevice.stylus.StylusFeatureProvider
|
||||||
@@ -126,8 +125,6 @@ class FakeFeatureFactory : FeatureFactory() {
|
|||||||
get() = TODO("Not yet implemented")
|
get() = TODO("Not yet implemented")
|
||||||
override val fingerprintFeatureProvider: FingerprintFeatureProvider
|
override val fingerprintFeatureProvider: FingerprintFeatureProvider
|
||||||
get() = TODO("Not yet implemented")
|
get() = TODO("Not yet implemented")
|
||||||
override val biometricsRepositoryProvider: BiometricsRepositoryProvider
|
|
||||||
get() = TODO("Not yet implemented")
|
|
||||||
override val wifiTrackerLibProvider: WifiTrackerLibProvider
|
override val wifiTrackerLibProvider: WifiTrackerLibProvider
|
||||||
get() = TODO("Not yet implemented")
|
get() = TODO("Not yet implemented")
|
||||||
override val securitySettingsFeatureProvider: SecuritySettingsFeatureProvider
|
override val securitySettingsFeatureProvider: SecuritySettingsFeatureProvider
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
include /src/com/android/settings/biometrics/OWNERS
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.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.utils.FingerprintRepositoryUtils.newFingerprintRepository;
|
|
||||||
import static com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.setupFingerprintEnrolledFingerprints;
|
|
||||||
import static com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.setupSuwMaxFingerprintsEnrollable;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.doAnswer;
|
|
||||||
import static org.mockito.Mockito.doReturn;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.hardware.biometrics.SensorProperties;
|
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
|
||||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
|
||||||
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class FingerprintRepositoryTest {
|
|
||||||
|
|
||||||
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
|
||||||
|
|
||||||
@Mock private Resources mResources;
|
|
||||||
@Mock private FingerprintManager mFingerprintManager;
|
|
||||||
|
|
||||||
private Context mContext;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
mContext = ApplicationProvider.getApplicationContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCanAssumeSensorType_forUnknownSensor() {
|
|
||||||
final FingerprintRepository repository = newFingerprintRepository(mFingerprintManager,
|
|
||||||
TYPE_UNKNOWN, 1);
|
|
||||||
assertThat(repository.canAssumeUdfps()).isFalse();
|
|
||||||
assertThat(repository.canAssumeSfps()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCanAssumeSensorType_forRearSensor() {
|
|
||||||
final FingerprintRepository repository = newFingerprintRepository(mFingerprintManager,
|
|
||||||
TYPE_REAR, 1);
|
|
||||||
assertThat(repository.canAssumeUdfps()).isFalse();
|
|
||||||
assertThat(repository.canAssumeSfps()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCanAssumeSensorType_forUdfpsUltrasonicSensor() {
|
|
||||||
final FingerprintRepository repository = newFingerprintRepository(mFingerprintManager,
|
|
||||||
TYPE_UDFPS_ULTRASONIC, 1);
|
|
||||||
assertThat(repository.canAssumeUdfps()).isTrue();
|
|
||||||
assertThat(repository.canAssumeSfps()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCanAssumeSensorType_forUdfpsOpticalSensor() {
|
|
||||||
final FingerprintRepository repository = newFingerprintRepository(mFingerprintManager,
|
|
||||||
TYPE_UDFPS_OPTICAL, 1);
|
|
||||||
assertThat(repository.canAssumeUdfps()).isTrue();
|
|
||||||
assertThat(repository.canAssumeSfps()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCanAssumeSensorType_forPowerButtonSensor() {
|
|
||||||
final FingerprintRepository repository = newFingerprintRepository(mFingerprintManager,
|
|
||||||
TYPE_POWER_BUTTON, 1);
|
|
||||||
assertThat(repository.canAssumeUdfps()).isFalse();
|
|
||||||
assertThat(repository.canAssumeSfps()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCanAssumeSensorType_forHomeButtonSensor() {
|
|
||||||
final FingerprintRepository repository = newFingerprintRepository(mFingerprintManager,
|
|
||||||
TYPE_HOME_BUTTON, 1);
|
|
||||||
assertThat(repository.canAssumeUdfps()).isFalse();
|
|
||||||
assertThat(repository.canAssumeSfps()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetMaxFingerprints() {
|
|
||||||
final FingerprintRepository repository = newFingerprintRepository(mFingerprintManager,
|
|
||||||
TYPE_UNKNOWN, 999);
|
|
||||||
assertThat(repository.getMaxFingerprints()).isEqualTo(999);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetNumOfEnrolledFingerprintsSize() {
|
|
||||||
final FingerprintRepository repository = newFingerprintRepository(mFingerprintManager,
|
|
||||||
TYPE_UNKNOWN, 999);
|
|
||||||
setupFingerprintEnrolledFingerprints(mFingerprintManager, 10, 3);
|
|
||||||
setupFingerprintEnrolledFingerprints(mFingerprintManager, 22, 99);
|
|
||||||
|
|
||||||
assertThat(repository.getNumOfEnrolledFingerprintsSize(10)).isEqualTo(3);
|
|
||||||
assertThat(repository.getNumOfEnrolledFingerprintsSize(22)).isEqualTo(99);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetMaxFingerprintsInSuw() {
|
|
||||||
final FingerprintRepository repository = newFingerprintRepository(mFingerprintManager,
|
|
||||||
TYPE_UNKNOWN, 999);
|
|
||||||
setupSuwMaxFingerprintsEnrollable(mContext, mResources, 333);
|
|
||||||
assertThat(repository.getMaxFingerprintsInSuw(mResources))
|
|
||||||
.isEqualTo(333);
|
|
||||||
|
|
||||||
setupSuwMaxFingerprintsEnrollable(mContext, mResources, 20);
|
|
||||||
assertThat(repository.getMaxFingerprintsInSuw(mResources)).isEqualTo(20);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetFirstFingerprintSensorPropertiesInternal() {
|
|
||||||
final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
|
|
||||||
final FingerprintSensorPropertiesInternal prop = new FingerprintSensorPropertiesInternal(
|
|
||||||
0 /* sensorId */,
|
|
||||||
SensorProperties.STRENGTH_STRONG,
|
|
||||||
5,
|
|
||||||
new ArrayList<>() /* componentInfo */,
|
|
||||||
TYPE_UDFPS_OPTICAL,
|
|
||||||
true /* resetLockoutRequiresHardwareAuthToken */);
|
|
||||||
props.add(prop);
|
|
||||||
doAnswer(invocation -> {
|
|
||||||
final IFingerprintAuthenticatorsRegisteredCallback callback =
|
|
||||||
invocation.getArgument(0);
|
|
||||||
callback.onAllAuthenticatorsRegistered(props);
|
|
||||||
return null;
|
|
||||||
}).when(mFingerprintManager).addAuthenticatorsRegisteredCallback(any());
|
|
||||||
|
|
||||||
final FingerprintRepository repository = new FingerprintRepository(mFingerprintManager);
|
|
||||||
assertThat(repository.getFirstFingerprintSensorPropertiesInternal()).isEqualTo(prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetEnrollStageCount() {
|
|
||||||
final FingerprintRepository repository = newFingerprintRepository(mFingerprintManager,
|
|
||||||
TYPE_UNKNOWN, 999);
|
|
||||||
|
|
||||||
final int expectedValue = 24;
|
|
||||||
doReturn(expectedValue).when(mFingerprintManager).getEnrollStageCount();
|
|
||||||
|
|
||||||
assertThat(repository.getEnrollStageCount()).isEqualTo(expectedValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetEnrollStageThreshold() {
|
|
||||||
final FingerprintRepository repository = newFingerprintRepository(mFingerprintManager,
|
|
||||||
TYPE_UNKNOWN, 999);
|
|
||||||
|
|
||||||
final float expectedValue0 = 0.42f;
|
|
||||||
final float expectedValue1 = 0.24f;
|
|
||||||
final float expectedValue2 = 0.33f;
|
|
||||||
final float expectedValue3 = 0.90f;
|
|
||||||
doReturn(expectedValue0).when(mFingerprintManager).getEnrollStageThreshold(0);
|
|
||||||
doReturn(expectedValue1).when(mFingerprintManager).getEnrollStageThreshold(1);
|
|
||||||
doReturn(expectedValue2).when(mFingerprintManager).getEnrollStageThreshold(2);
|
|
||||||
doReturn(expectedValue3).when(mFingerprintManager).getEnrollStageThreshold(3);
|
|
||||||
|
|
||||||
assertThat(repository.getEnrollStageThreshold(2)).isEqualTo(expectedValue2);
|
|
||||||
assertThat(repository.getEnrollStageThreshold(1)).isEqualTo(expectedValue1);
|
|
||||||
assertThat(repository.getEnrollStageThreshold(3)).isEqualTo(expectedValue3);
|
|
||||||
assertThat(repository.getEnrollStageThreshold(0)).isEqualTo(expectedValue0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.model
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.SystemClock
|
|
||||||
import android.os.UserHandle
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import com.android.settings.biometrics.BiometricEnrollBase
|
|
||||||
import com.android.settings.password.ChooseLockSettingsHelper
|
|
||||||
import com.google.common.truth.Truth
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import java.util.Arrays
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class CredentialModelTest {
|
|
||||||
|
|
||||||
private val clock = SystemClock.elapsedRealtimeClock()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testNullBundle() {
|
|
||||||
val credentialModel = CredentialModel(null, clock)
|
|
||||||
Truth.assertThat(credentialModel.userId).isEqualTo(UserHandle.myUserId())
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun newCredentialModelIntentExtras(
|
|
||||||
userId: Int, challenge: Long,
|
|
||||||
token: ByteArray?, gkPwHandle: Long
|
|
||||||
): Bundle {
|
|
||||||
val bundle = Bundle()
|
|
||||||
bundle.putInt(Intent.EXTRA_USER_ID, userId)
|
|
||||||
bundle.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge)
|
|
||||||
bundle.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token)
|
|
||||||
bundle.putLong(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle)
|
|
||||||
return bundle
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun newValidTokenCredentialIntentExtras(userId: Int): Bundle {
|
|
||||||
return newCredentialModelIntentExtras(
|
|
||||||
userId, 1L, byteArrayOf(0, 1, 2),
|
|
||||||
CredentialModel.INVALID_GK_PW_HANDLE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun newOnlySensorValidCredentialIntentExtras(userId: Int): Bundle {
|
|
||||||
return newCredentialModelIntentExtras(
|
|
||||||
userId, CredentialModel.INVALID_CHALLENGE, null,
|
|
||||||
CredentialModel.INVALID_GK_PW_HANDLE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun newGkPwHandleCredentialIntentExtras(userId: Int, gkPwHandle: Long): Bundle {
|
|
||||||
return newCredentialModelIntentExtras(
|
|
||||||
userId,
|
|
||||||
CredentialModel.INVALID_CHALLENGE,
|
|
||||||
null,
|
|
||||||
gkPwHandle
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkBundleLongValue(
|
|
||||||
bundle1: Bundle, bundle2: Bundle,
|
|
||||||
key: String
|
|
||||||
) {
|
|
||||||
if (!bundle1.containsKey(key)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val value1 = bundle1.getInt(key)
|
|
||||||
val value2 = bundle2.getInt(key)
|
|
||||||
Truth.assertWithMessage(
|
|
||||||
"bundle not match, key:" + key + ", value1:" + value1 + ", value2:"
|
|
||||||
+ value2
|
|
||||||
).that(value1).isEqualTo(value2)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkBundleIntValue(
|
|
||||||
bundle1: Bundle, bundle2: Bundle,
|
|
||||||
key: String
|
|
||||||
) {
|
|
||||||
if (!bundle1.containsKey(key)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val value1 = bundle1.getLong(key)
|
|
||||||
val value2 = bundle2.getLong(key)
|
|
||||||
Truth.assertWithMessage(
|
|
||||||
"bundle not match, key:" + key + ", value1:" + value1 + ", value2:"
|
|
||||||
+ value2
|
|
||||||
).that(value1).isEqualTo(value2)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkBundleByteArrayValue(
|
|
||||||
bundle1: Bundle, bundle2: Bundle,
|
|
||||||
key: String
|
|
||||||
) {
|
|
||||||
if (!bundle1.containsKey(key)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val value1 = bundle1.getByteArray(key)
|
|
||||||
val value2 = bundle2.getByteArray(key)
|
|
||||||
val errMsg = ("bundle not match, key:" + key + ", value1:" + Arrays.toString(value1)
|
|
||||||
+ ", value2:" + Arrays.toString(value2))
|
|
||||||
if (value1 == null) {
|
|
||||||
Truth.assertWithMessage(errMsg).that(value2).isNull()
|
|
||||||
} else {
|
|
||||||
Truth.assertWithMessage(errMsg).that(value1.size).isEqualTo(
|
|
||||||
value2!!.size
|
|
||||||
)
|
|
||||||
for (i in value1.indices) {
|
|
||||||
Truth.assertWithMessage(errMsg).that(value1[i]).isEqualTo(
|
|
||||||
value2[i]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.model
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.test.core.app.ApplicationProvider
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import com.android.settings.biometrics.BiometricEnrollActivity
|
|
||||||
import com.google.android.setupcompat.util.WizardManagerHelper
|
|
||||||
import com.google.common.truth.Truth
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class EnrollmentRequestTest {
|
|
||||||
|
|
||||||
private val context = ApplicationProvider.getApplicationContext<Context>()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testIsSuw() {
|
|
||||||
// Default false
|
|
||||||
Truth.assertThat(EnrollmentRequest(Intent(), context, true).isSuw).isFalse()
|
|
||||||
Truth.assertThat(EnrollmentRequest(Intent(), context, false).isSuw).isFalse()
|
|
||||||
val trueIntent = Intent()
|
|
||||||
trueIntent.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true)
|
|
||||||
Truth.assertThat(EnrollmentRequest(trueIntent, context, true).isSuw).isTrue()
|
|
||||||
Truth.assertThat(EnrollmentRequest(trueIntent, context, false).isSuw).isFalse()
|
|
||||||
val falseIntent = Intent()
|
|
||||||
trueIntent.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, false)
|
|
||||||
Truth.assertThat(EnrollmentRequest(falseIntent, context, true).isSuw).isFalse()
|
|
||||||
Truth.assertThat(EnrollmentRequest(falseIntent, context, false).isSuw).isFalse()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testIsAfterSuwOrSuwSuggestedAction() {
|
|
||||||
// Default false
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(Intent(), context, true)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isFalse()
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(Intent(), context, false)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isFalse()
|
|
||||||
val deferredTrueIntent = Intent()
|
|
||||||
deferredTrueIntent.putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, true)
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(deferredTrueIntent, context, true)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isTrue()
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(deferredTrueIntent, context, false)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isFalse()
|
|
||||||
val deferredFalseIntent = Intent()
|
|
||||||
deferredFalseIntent.putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, false)
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(deferredFalseIntent, context, false)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isFalse()
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(deferredFalseIntent, context, false)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isFalse()
|
|
||||||
val portalTrueIntent = Intent()
|
|
||||||
portalTrueIntent.putExtra(WizardManagerHelper.EXTRA_IS_PORTAL_SETUP, true)
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(portalTrueIntent, context, true)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isTrue()
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(portalTrueIntent, context, false)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isFalse()
|
|
||||||
val portalFalseIntent = Intent()
|
|
||||||
portalFalseIntent.putExtra(WizardManagerHelper.EXTRA_IS_PORTAL_SETUP, false)
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(portalFalseIntent, context, false)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isFalse()
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(portalFalseIntent, context, false)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isFalse()
|
|
||||||
val suggestedTrueIntent = Intent()
|
|
||||||
suggestedTrueIntent.putExtra(WizardManagerHelper.EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW, true)
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(suggestedTrueIntent, context, true)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isTrue()
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(suggestedTrueIntent, context, false)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isFalse()
|
|
||||||
val suggestedFalseIntent = Intent()
|
|
||||||
suggestedFalseIntent.putExtra(WizardManagerHelper.EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW, false)
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(suggestedFalseIntent, context, false)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isFalse()
|
|
||||||
Truth.assertThat(
|
|
||||||
EnrollmentRequest(suggestedFalseIntent, context, false)
|
|
||||||
.isAfterSuwOrSuwSuggestedAction
|
|
||||||
).isFalse()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGetSuwExtras_inSuw() {
|
|
||||||
val suwIntent = Intent()
|
|
||||||
suwIntent.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true)
|
|
||||||
val setupRequest = EnrollmentRequest(suwIntent, context, true)
|
|
||||||
val bundle = setupRequest.suwExtras
|
|
||||||
Truth.assertThat(bundle).isNotNull()
|
|
||||||
Truth.assertThat(bundle.size()).isAtLeast(1)
|
|
||||||
Truth.assertThat(bundle.getBoolean(WizardManagerHelper.EXTRA_IS_SETUP_FLOW)).isTrue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGetSuwExtras_notInSuw() {
|
|
||||||
val suwIntent = Intent()
|
|
||||||
suwIntent.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true)
|
|
||||||
val setupRequest = EnrollmentRequest(suwIntent, context, false)
|
|
||||||
val bundle = setupRequest.suwExtras
|
|
||||||
Truth.assertThat(bundle).isNotNull()
|
|
||||||
Truth.assertThat(bundle.size()).isEqualTo(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testIsSkipIntro() {
|
|
||||||
// Default false
|
|
||||||
Truth.assertThat(EnrollmentRequest(Intent(), context, true).isSkipIntro).isFalse()
|
|
||||||
Truth.assertThat(EnrollmentRequest(Intent(), context, false).isSkipIntro).isFalse()
|
|
||||||
val trueIntent = Intent()
|
|
||||||
trueIntent.putExtra(BiometricEnrollActivity.EXTRA_SKIP_INTRO, true)
|
|
||||||
Truth.assertThat(EnrollmentRequest(trueIntent, context, true).isSkipIntro).isTrue()
|
|
||||||
Truth.assertThat(EnrollmentRequest(trueIntent, context, false).isSkipIntro).isTrue()
|
|
||||||
val falseIntent = Intent()
|
|
||||||
falseIntent.putExtra(BiometricEnrollActivity.EXTRA_SKIP_INTRO, false)
|
|
||||||
Truth.assertThat(EnrollmentRequest(falseIntent, context, false).isSkipIntro).isFalse()
|
|
||||||
Truth.assertThat(EnrollmentRequest(falseIntent, context, false).isSkipIntro).isFalse()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testIsSkipFindSensor() {
|
|
||||||
// Default false
|
|
||||||
Truth.assertThat(EnrollmentRequest(Intent(), context, true).isSkipFindSensor)
|
|
||||||
.isFalse()
|
|
||||||
Truth.assertThat(EnrollmentRequest(Intent(), context, false).isSkipFindSensor)
|
|
||||||
.isFalse()
|
|
||||||
val trueIntent = Intent()
|
|
||||||
trueIntent.putExtra(EnrollmentRequest.EXTRA_SKIP_FIND_SENSOR, true)
|
|
||||||
Truth.assertThat(EnrollmentRequest(trueIntent, context, true).isSkipFindSensor).isTrue()
|
|
||||||
Truth.assertThat(EnrollmentRequest(trueIntent, context, false).isSkipFindSensor).isTrue()
|
|
||||||
val falseIntent = Intent()
|
|
||||||
falseIntent.putExtra(EnrollmentRequest.EXTRA_SKIP_FIND_SENSOR, false)
|
|
||||||
Truth.assertThat(EnrollmentRequest(falseIntent, context, false).isSkipFindSensor)
|
|
||||||
.isFalse()
|
|
||||||
Truth.assertThat(EnrollmentRequest(falseIntent, context, false).isSkipFindSensor)
|
|
||||||
.isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,519 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.admin.DevicePolicyManager
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.SystemClock
|
|
||||||
import android.os.UserHandle
|
|
||||||
import androidx.activity.result.ActivityResult
|
|
||||||
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.biometrics.BiometricEnrollBase
|
|
||||||
import com.android.settings.biometrics2.ui.model.CredentialModel
|
|
||||||
import com.android.settings.biometrics2.ui.model.CredentialModelTest.Companion.newGkPwHandleCredentialIntentExtras
|
|
||||||
import com.android.settings.biometrics2.ui.model.CredentialModelTest.Companion.newOnlySensorValidCredentialIntentExtras
|
|
||||||
import com.android.settings.biometrics2.ui.model.CredentialModelTest.Companion.newValidTokenCredentialIntentExtras
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator
|
|
||||||
import com.android.settings.password.ChooseLockPattern
|
|
||||||
import com.android.settings.password.ChooseLockSettingsHelper
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
import kotlinx.coroutines.flow.toList
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.test.TestScope
|
|
||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
|
||||||
import kotlinx.coroutines.test.runCurrent
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.mockito.Mock
|
|
||||||
import org.mockito.Mockito
|
|
||||||
import org.mockito.junit.MockitoJUnit
|
|
||||||
import org.mockito.junit.MockitoRule
|
|
||||||
import org.mockito.Mockito.`when` as whenever
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class AutoCredentialViewModelTest {
|
|
||||||
|
|
||||||
@get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
|
|
||||||
|
|
||||||
@Mock private lateinit var lockPatternUtils: LockPatternUtils
|
|
||||||
|
|
||||||
private var challengeGenerator: TestChallengeGenerator = TestChallengeGenerator()
|
|
||||||
|
|
||||||
private lateinit var viewModel: AutoCredentialViewModel
|
|
||||||
private fun newAutoCredentialViewModel(bundle: Bundle?): AutoCredentialViewModel {
|
|
||||||
return AutoCredentialViewModel(
|
|
||||||
ApplicationProvider.getApplicationContext(),
|
|
||||||
lockPatternUtils,
|
|
||||||
challengeGenerator,
|
|
||||||
CredentialModel(bundle, SystemClock.elapsedRealtimeClock())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
challengeGenerator = TestChallengeGenerator()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupGenerateChallenge(userId: Int, newSensorId: Int, newChallenge: Long) {
|
|
||||||
whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
|
||||||
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
|
|
||||||
)
|
|
||||||
challengeGenerator.userId = userId
|
|
||||||
challengeGenerator.sensorId = newSensorId
|
|
||||||
challengeGenerator.challenge = newChallenge
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCheckCredential_validCredentialCase() = runTest {
|
|
||||||
val userId = 99
|
|
||||||
viewModel = newAutoCredentialViewModel(newValidTokenCredentialIntentExtras(userId))
|
|
||||||
whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
|
||||||
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
|
|
||||||
)
|
|
||||||
|
|
||||||
val generateFails = listOfGenerateChallengeFailedFlow()
|
|
||||||
|
|
||||||
// Run credential check
|
|
||||||
val action = viewModel.checkCredential(backgroundScope)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// Check viewModel behavior
|
|
||||||
assertThat(action).isEqualTo(CredentialAction.CREDENTIAL_VALID)
|
|
||||||
assertThat(generateFails.size).isEqualTo(0)
|
|
||||||
|
|
||||||
// Check createGeneratingChallengeExtras()
|
|
||||||
assertThat(viewModel.createGeneratingChallengeExtras()).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCheckCredential_needToChooseLock() = runTest {
|
|
||||||
val userId = 100
|
|
||||||
viewModel = newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
|
|
||||||
whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
|
||||||
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
|
|
||||||
)
|
|
||||||
|
|
||||||
val generateFails = listOfGenerateChallengeFailedFlow()
|
|
||||||
|
|
||||||
// Run credential check
|
|
||||||
val action = viewModel.checkCredential(backgroundScope)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// Check viewModel behavior
|
|
||||||
assertThat(action).isEqualTo(CredentialAction.FAIL_NEED_TO_CHOOSE_LOCK)
|
|
||||||
assertThat(generateFails.size).isEqualTo(0)
|
|
||||||
|
|
||||||
// Check createGeneratingChallengeExtras()
|
|
||||||
assertThat(viewModel.createGeneratingChallengeExtras()).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCheckCredential_needToConfirmLockForSomething() = runTest {
|
|
||||||
val userId = 101
|
|
||||||
viewModel =
|
|
||||||
newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
|
|
||||||
whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
|
||||||
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
|
|
||||||
)
|
|
||||||
|
|
||||||
val generateFails = listOfGenerateChallengeFailedFlow()
|
|
||||||
|
|
||||||
// Run credential check
|
|
||||||
val action = viewModel.checkCredential(backgroundScope)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// Check viewModel behavior
|
|
||||||
assertThat(action).isEqualTo(CredentialAction.FAIL_NEED_TO_CONFIRM_LOCK)
|
|
||||||
assertThat(generateFails.size).isEqualTo(0)
|
|
||||||
|
|
||||||
// Check createGeneratingChallengeExtras()
|
|
||||||
assertThat(viewModel.createGeneratingChallengeExtras()).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCheckCredential_needToConfirmLockForNumeric() = runTest {
|
|
||||||
val userId = 102
|
|
||||||
viewModel =
|
|
||||||
newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
|
|
||||||
whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
|
||||||
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
|
|
||||||
)
|
|
||||||
|
|
||||||
val generateFails = listOfGenerateChallengeFailedFlow()
|
|
||||||
|
|
||||||
// Run credential check
|
|
||||||
val action = viewModel.checkCredential(backgroundScope)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// Check viewModel behavior
|
|
||||||
assertThat(action).isEqualTo(CredentialAction.FAIL_NEED_TO_CONFIRM_LOCK)
|
|
||||||
assertThat(generateFails.size).isEqualTo(0)
|
|
||||||
|
|
||||||
// Check createGeneratingChallengeExtras()
|
|
||||||
assertThat(viewModel.createGeneratingChallengeExtras()).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCheckCredential_needToConfirmLockForAlphabetic() = runTest {
|
|
||||||
val userId = 103
|
|
||||||
viewModel =
|
|
||||||
newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
|
|
||||||
whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
|
||||||
DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
|
|
||||||
)
|
|
||||||
|
|
||||||
val generateFails = listOfGenerateChallengeFailedFlow()
|
|
||||||
|
|
||||||
// Run credential check
|
|
||||||
val action = viewModel.checkCredential(this)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// Check viewModel behavior
|
|
||||||
assertThat(action).isEqualTo(CredentialAction.FAIL_NEED_TO_CONFIRM_LOCK)
|
|
||||||
assertThat(generateFails.size).isEqualTo(0)
|
|
||||||
|
|
||||||
// Check createGeneratingChallengeExtras()
|
|
||||||
assertThat(viewModel.createGeneratingChallengeExtras()).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCheckCredential_generateChallenge() = runTest {
|
|
||||||
val userId = 104
|
|
||||||
val gkPwHandle = 1111L
|
|
||||||
viewModel =
|
|
||||||
newAutoCredentialViewModel(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))
|
|
||||||
whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
|
||||||
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
|
|
||||||
)
|
|
||||||
val newSensorId = 10
|
|
||||||
val newChallenge = 20L
|
|
||||||
setupGenerateChallenge(userId, newSensorId, newChallenge)
|
|
||||||
whenever(
|
|
||||||
lockPatternUtils.verifyGatekeeperPasswordHandle(
|
|
||||||
gkPwHandle,
|
|
||||||
newChallenge,
|
|
||||||
userId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.thenReturn(newGoodCredential(gkPwHandle, byteArrayOf(1)))
|
|
||||||
val hasCalledRemoveGkPwHandle = AtomicBoolean()
|
|
||||||
Mockito.doAnswer {
|
|
||||||
hasCalledRemoveGkPwHandle.set(true)
|
|
||||||
null
|
|
||||||
}.`when`(lockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle)
|
|
||||||
|
|
||||||
val generateFails = listOfGenerateChallengeFailedFlow()
|
|
||||||
|
|
||||||
// Run credential check
|
|
||||||
val action = viewModel.checkCredential(backgroundScope)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// Check viewModel behavior
|
|
||||||
assertThat(action).isEqualTo(CredentialAction.IS_GENERATING_CHALLENGE)
|
|
||||||
assertThat(generateFails.size).isEqualTo(0)
|
|
||||||
|
|
||||||
// Check data inside CredentialModel
|
|
||||||
assertThat(viewModel.token).isNotNull()
|
|
||||||
assertThat(challengeGenerator.callbackRunCount).isEqualTo(1)
|
|
||||||
assertThat(hasCalledRemoveGkPwHandle.get()).isFalse()
|
|
||||||
|
|
||||||
// Check createGeneratingChallengeExtras()
|
|
||||||
val generatingChallengeExtras = viewModel.createGeneratingChallengeExtras()
|
|
||||||
assertThat(generatingChallengeExtras).isNotNull()
|
|
||||||
assertThat(generatingChallengeExtras!!.getLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE))
|
|
||||||
.isEqualTo(newChallenge)
|
|
||||||
val tokens =
|
|
||||||
generatingChallengeExtras.getByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
|
|
||||||
assertThat(tokens).isNotNull()
|
|
||||||
assertThat(tokens!!.size).isEqualTo(1)
|
|
||||||
assertThat(tokens[0]).isEqualTo(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCheckCredential_generateChallengeFail() = runTest {
|
|
||||||
backgroundScope.launch {
|
|
||||||
val userId = 104
|
|
||||||
val gkPwHandle = 1111L
|
|
||||||
viewModel =
|
|
||||||
newAutoCredentialViewModel(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))
|
|
||||||
whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
|
||||||
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
|
|
||||||
)
|
|
||||||
val newSensorId = 10
|
|
||||||
val newChallenge = 20L
|
|
||||||
setupGenerateChallenge(userId, newSensorId, newChallenge)
|
|
||||||
whenever(
|
|
||||||
lockPatternUtils.verifyGatekeeperPasswordHandle(
|
|
||||||
gkPwHandle,
|
|
||||||
newChallenge,
|
|
||||||
userId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.thenReturn(newBadCredential(0))
|
|
||||||
|
|
||||||
val generateFails = listOfGenerateChallengeFailedFlow()
|
|
||||||
|
|
||||||
// Run credential check
|
|
||||||
val action = viewModel.checkCredential(this)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(action).isEqualTo(CredentialAction.IS_GENERATING_CHALLENGE)
|
|
||||||
assertThat(generateFails.size).isEqualTo(1)
|
|
||||||
assertThat(generateFails[0]).isEqualTo(true)
|
|
||||||
assertThat(challengeGenerator.callbackRunCount).isEqualTo(1)
|
|
||||||
|
|
||||||
// Check createGeneratingChallengeExtras()
|
|
||||||
assertThat(viewModel.createGeneratingChallengeExtras()).isNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGetUserId_fromIntent() {
|
|
||||||
val userId = 106
|
|
||||||
viewModel = newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
|
|
||||||
|
|
||||||
// Get userId
|
|
||||||
assertThat(viewModel.userId).isEqualTo(userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGenerateChallengeAsCredentialActivityResult_invalidChooseLock() = runTest {
|
|
||||||
backgroundScope.launch {
|
|
||||||
val userId = 107
|
|
||||||
val gkPwHandle = 3333L
|
|
||||||
viewModel =
|
|
||||||
newAutoCredentialViewModel(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))
|
|
||||||
val intent = Intent()
|
|
||||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle)
|
|
||||||
|
|
||||||
val generateFails = listOfGenerateChallengeFailedFlow()
|
|
||||||
|
|
||||||
// Run generateChallengeAsCredentialActivityResult()
|
|
||||||
val ret = viewModel.generateChallengeAsCredentialActivityResult(
|
|
||||||
true,
|
|
||||||
ActivityResult(ChooseLockPattern.RESULT_FINISHED + 1, intent),
|
|
||||||
backgroundScope
|
|
||||||
)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(ret).isFalse()
|
|
||||||
assertThat(generateFails.size).isEqualTo(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGenerateChallengeAsCredentialActivityResult_invalidConfirmLock() = runTest {
|
|
||||||
backgroundScope.launch {
|
|
||||||
val userId = 107
|
|
||||||
val gkPwHandle = 3333L
|
|
||||||
viewModel =
|
|
||||||
newAutoCredentialViewModel(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))
|
|
||||||
val intent = Intent()
|
|
||||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle)
|
|
||||||
|
|
||||||
val generateFails = listOfGenerateChallengeFailedFlow()
|
|
||||||
|
|
||||||
// Run generateChallengeAsCredentialActivityResult()
|
|
||||||
val ret = viewModel.generateChallengeAsCredentialActivityResult(
|
|
||||||
false,
|
|
||||||
ActivityResult(Activity.RESULT_OK + 1, intent),
|
|
||||||
backgroundScope
|
|
||||||
)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(ret).isFalse()
|
|
||||||
assertThat(generateFails.size).isEqualTo(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGenerateChallengeAsCredentialActivityResult_nullDataChooseLock() = runTest {
|
|
||||||
val userId = 108
|
|
||||||
val gkPwHandle = 4444L
|
|
||||||
viewModel =
|
|
||||||
newAutoCredentialViewModel(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))
|
|
||||||
|
|
||||||
val generateFails = listOfGenerateChallengeFailedFlow()
|
|
||||||
|
|
||||||
// Run generateChallengeAsCredentialActivityResult()
|
|
||||||
val ret = viewModel.generateChallengeAsCredentialActivityResult(
|
|
||||||
true,
|
|
||||||
ActivityResult(ChooseLockPattern.RESULT_FINISHED, null),
|
|
||||||
backgroundScope
|
|
||||||
)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(ret).isFalse()
|
|
||||||
assertThat(generateFails.size).isEqualTo(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGenerateChallengeAsCredentialActivityResult_nullDataConfirmLock() = runTest {
|
|
||||||
val userId = 109
|
|
||||||
viewModel =
|
|
||||||
newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
|
|
||||||
|
|
||||||
val generateFails = listOfGenerateChallengeFailedFlow()
|
|
||||||
|
|
||||||
// Run generateChallengeAsCredentialActivityResult()
|
|
||||||
val ret = viewModel.generateChallengeAsCredentialActivityResult(
|
|
||||||
false,
|
|
||||||
ActivityResult(Activity.RESULT_OK, null),
|
|
||||||
backgroundScope
|
|
||||||
)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(ret).isFalse()
|
|
||||||
assertThat(generateFails.size).isEqualTo(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGenerateChallengeAsCredentialActivityResult_validChooseLock() = runTest {
|
|
||||||
val userId = 108
|
|
||||||
viewModel =
|
|
||||||
newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
|
|
||||||
whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
|
||||||
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
|
|
||||||
)
|
|
||||||
val gkPwHandle = 6666L
|
|
||||||
val newSensorId = 50
|
|
||||||
val newChallenge = 60L
|
|
||||||
setupGenerateChallenge(userId, newSensorId, newChallenge)
|
|
||||||
whenever(
|
|
||||||
lockPatternUtils.verifyGatekeeperPasswordHandle(
|
|
||||||
gkPwHandle,
|
|
||||||
newChallenge,
|
|
||||||
userId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.thenReturn(newGoodCredential(gkPwHandle, byteArrayOf(1)))
|
|
||||||
val hasCalledRemoveGkPwHandle = AtomicBoolean()
|
|
||||||
Mockito.doAnswer {
|
|
||||||
hasCalledRemoveGkPwHandle.set(true)
|
|
||||||
null
|
|
||||||
}.`when`(lockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle)
|
|
||||||
|
|
||||||
val generateFails = listOfGenerateChallengeFailedFlow()
|
|
||||||
|
|
||||||
// Run generateChallengeAsCredentialActivityResult()
|
|
||||||
val intent =
|
|
||||||
Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle)
|
|
||||||
val ret = viewModel.generateChallengeAsCredentialActivityResult(
|
|
||||||
true,
|
|
||||||
ActivityResult(ChooseLockPattern.RESULT_FINISHED, intent),
|
|
||||||
backgroundScope
|
|
||||||
)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(ret).isTrue()
|
|
||||||
assertThat(generateFails.size).isEqualTo(0)
|
|
||||||
assertThat(viewModel.token).isNotNull()
|
|
||||||
assertThat(challengeGenerator.callbackRunCount).isEqualTo(1)
|
|
||||||
assertThat(hasCalledRemoveGkPwHandle.get()).isTrue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGenerateChallengeAsCredentialActivityResult_validConfirmLock() = runTest {
|
|
||||||
val userId = 109
|
|
||||||
viewModel =
|
|
||||||
newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
|
|
||||||
whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
|
|
||||||
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
|
|
||||||
)
|
|
||||||
val gkPwHandle = 5555L
|
|
||||||
val newSensorId = 80
|
|
||||||
val newChallenge = 90L
|
|
||||||
setupGenerateChallenge(userId, newSensorId, newChallenge)
|
|
||||||
whenever(
|
|
||||||
lockPatternUtils.verifyGatekeeperPasswordHandle(
|
|
||||||
gkPwHandle,
|
|
||||||
newChallenge,
|
|
||||||
userId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.thenReturn(newGoodCredential(gkPwHandle, byteArrayOf(1)))
|
|
||||||
val hasCalledRemoveGkPwHandle = AtomicBoolean()
|
|
||||||
Mockito.doAnswer {
|
|
||||||
hasCalledRemoveGkPwHandle.set(true)
|
|
||||||
null
|
|
||||||
}.`when`(lockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle)
|
|
||||||
|
|
||||||
val generateFails = listOfGenerateChallengeFailedFlow()
|
|
||||||
|
|
||||||
// Run generateChallengeAsCredentialActivityResult()
|
|
||||||
val intent =
|
|
||||||
Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle)
|
|
||||||
val ret = viewModel.generateChallengeAsCredentialActivityResult(
|
|
||||||
false,
|
|
||||||
ActivityResult(Activity.RESULT_OK, intent),
|
|
||||||
backgroundScope
|
|
||||||
)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(ret).isTrue()
|
|
||||||
assertThat(generateFails.size).isEqualTo(0)
|
|
||||||
assertThat(viewModel.token).isNotNull()
|
|
||||||
assertThat(challengeGenerator.callbackRunCount).isEqualTo(1)
|
|
||||||
assertThat(hasCalledRemoveGkPwHandle.get()).isTrue()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun TestScope.listOfGenerateChallengeFailedFlow(): List<Boolean> =
|
|
||||||
mutableListOf<Boolean>().also {
|
|
||||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
|
||||||
viewModel.generateChallengeFailedFlow.toList(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestChallengeGenerator : ChallengeGenerator {
|
|
||||||
var sensorId = -1
|
|
||||||
var userId = UserHandle.myUserId()
|
|
||||||
var challenge = CredentialModel.INVALID_CHALLENGE
|
|
||||||
var callbackRunCount = 0
|
|
||||||
|
|
||||||
override var callback: AutoCredentialViewModel.GenerateChallengeCallback? = null
|
|
||||||
|
|
||||||
override fun generateChallenge(userId: Int) {
|
|
||||||
callback?.let {
|
|
||||||
it.onChallengeGenerated(sensorId, this.userId, challenge)
|
|
||||||
++callbackRunCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun newGoodCredential(gkPwHandle: Long, hat: ByteArray): VerifyCredentialResponse {
|
|
||||||
return VerifyCredentialResponse.Builder()
|
|
||||||
.setGatekeeperPasswordHandle(gkPwHandle)
|
|
||||||
.setGatekeeperHAT(hat)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun newBadCredential(timeout: Int): VerifyCredentialResponse {
|
|
||||||
return if (timeout > 0) {
|
|
||||||
VerifyCredentialResponse.fromTimeout(timeout)
|
|
||||||
} else {
|
|
||||||
VerifyCredentialResponse.fromError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import com.android.settings.testutils.InstantTaskExecutorRule;
|
|
||||||
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class DeviceFoldedViewModelTest {
|
|
||||||
|
|
||||||
@Rule public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();
|
|
||||||
|
|
||||||
private DeviceFoldedViewModel mViewModel;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
final Application application = ApplicationProvider.getApplicationContext();
|
|
||||||
mViewModel = new DeviceFoldedViewModel(new ScreenSizeFoldProvider(application),
|
|
||||||
application.getMainExecutor());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLiveData() {
|
|
||||||
final Configuration config1 = new Configuration();
|
|
||||||
config1.smallestScreenWidthDp = 601;
|
|
||||||
mViewModel.onConfigurationChanged(config1);
|
|
||||||
assertThat(mViewModel.getLiveData().getValue()).isFalse();
|
|
||||||
|
|
||||||
final Configuration config2 = new Configuration();
|
|
||||||
config2.smallestScreenWidthDp = 599;
|
|
||||||
mViewModel.onConfigurationChanged(config2);
|
|
||||||
assertThat(mViewModel.getLiveData().getValue()).isTrue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.view.Surface;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
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.junit.MockitoJUnit;
|
|
||||||
import org.mockito.junit.MockitoRule;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class DeviceRotationViewModelTest {
|
|
||||||
|
|
||||||
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
|
||||||
@Rule public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();
|
|
||||||
|
|
||||||
private TestDeviceRotationViewModel mViewModel;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
TestDeviceRotationViewModel.sTestRotation = 3;
|
|
||||||
mViewModel = new TestDeviceRotationViewModel(ApplicationProvider.getApplicationContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDefaultLiveDataNotNull() {
|
|
||||||
assertThat(mViewModel.getLiveData().getValue()).isEqualTo(mViewModel.sTestRotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOnDisplayChange() {
|
|
||||||
mViewModel.sTestRotation = 3;
|
|
||||||
mViewModel.triggerOnDisplayChanged();
|
|
||||||
assertThat(mViewModel.getLiveData().getValue()).isEqualTo(mViewModel.sTestRotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TestDeviceRotationViewModel extends DeviceRotationViewModel {
|
|
||||||
|
|
||||||
@Surface.Rotation static int sTestRotation = 0;
|
|
||||||
|
|
||||||
public TestDeviceRotationViewModel(@NonNull Application application) {
|
|
||||||
super(application);
|
|
||||||
}
|
|
||||||
|
|
||||||
void triggerOnDisplayChanged() {
|
|
||||||
mDisplayListener.onDisplayChanged(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getRotation() {
|
|
||||||
return sTestRotation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel;
|
|
||||||
|
|
||||||
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
|
|
||||||
|
|
||||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_ICON_TOUCH_DIALOG;
|
|
||||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_BACK_PRESSED;
|
|
||||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_USER_SKIP;
|
|
||||||
import static com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.newFingerprintRepository;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.doAnswer;
|
|
||||||
import static org.mockito.Mockito.doReturn;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.hardware.biometrics.SensorProperties;
|
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
|
||||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
|
||||||
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
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.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;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class FingerprintEnrollEnrollingViewModelTest {
|
|
||||||
|
|
||||||
private static final int TEST_USER_ID = 33;
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public final MockitoRule mockito = MockitoJUnit.rule();
|
|
||||||
@Rule
|
|
||||||
public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private FingerprintManager mFingerprintManager;
|
|
||||||
|
|
||||||
private Application mApplication;
|
|
||||||
private FingerprintEnrollEnrollingViewModel mViewModel;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
mApplication = ApplicationProvider.getApplicationContext();
|
|
||||||
mViewModel = new FingerprintEnrollEnrollingViewModel(
|
|
||||||
mApplication,
|
|
||||||
TEST_USER_ID,
|
|
||||||
newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIconTouchDialog() {
|
|
||||||
final LiveData<Integer> actionLiveData = mViewModel.getActionLiveData();
|
|
||||||
assertThat(actionLiveData.getValue()).isEqualTo(null);
|
|
||||||
|
|
||||||
mViewModel.showIconTouchDialog();
|
|
||||||
assertThat(actionLiveData.getValue()).isEqualTo(
|
|
||||||
FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_ICON_TOUCH_DIALOG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void tesBackPressedScenario() {
|
|
||||||
final LiveData<Integer> actionLiveData = mViewModel.getActionLiveData();
|
|
||||||
assertThat(actionLiveData.getValue()).isEqualTo(null);
|
|
||||||
assertThat(mViewModel.getOnBackPressed()).isEqualTo(false);
|
|
||||||
|
|
||||||
mViewModel.setOnBackPressed();
|
|
||||||
assertThat(mViewModel.getOnBackPressed()).isEqualTo(true);
|
|
||||||
|
|
||||||
mViewModel.onCancelledDueToOnBackPressed();
|
|
||||||
assertThat(mViewModel.getOnBackPressed()).isEqualTo(false);
|
|
||||||
assertThat(actionLiveData.getValue()).isEqualTo(
|
|
||||||
FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_BACK_PRESSED);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSkipPressedScenario() {
|
|
||||||
final LiveData<Integer> actionLiveData = mViewModel.getActionLiveData();
|
|
||||||
assertThat(actionLiveData.getValue()).isEqualTo(null);
|
|
||||||
assertThat(mViewModel.getOnSkipPressed()).isEqualTo(false);
|
|
||||||
|
|
||||||
mViewModel.setOnSkipPressed();
|
|
||||||
assertThat(mViewModel.getOnSkipPressed()).isEqualTo(true);
|
|
||||||
|
|
||||||
mViewModel.onCancelledDueToOnSkipPressed();
|
|
||||||
assertThat(mViewModel.getOnSkipPressed()).isEqualTo(false);
|
|
||||||
assertThat(actionLiveData.getValue()).isEqualTo(
|
|
||||||
FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_USER_SKIP);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetFirstFingerprintSensorPropertiesInternal() {
|
|
||||||
final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
|
|
||||||
final FingerprintSensorPropertiesInternal prop = new FingerprintSensorPropertiesInternal(
|
|
||||||
0 /* sensorId */,
|
|
||||||
SensorProperties.STRENGTH_STRONG,
|
|
||||||
5,
|
|
||||||
new ArrayList<>() /* componentInfo */,
|
|
||||||
TYPE_UDFPS_OPTICAL,
|
|
||||||
true /* resetLockoutRequiresHardwareAuthToken */);
|
|
||||||
props.add(prop);
|
|
||||||
doAnswer(invocation -> {
|
|
||||||
final IFingerprintAuthenticatorsRegisteredCallback callback =
|
|
||||||
invocation.getArgument(0);
|
|
||||||
callback.onAllAuthenticatorsRegistered(props);
|
|
||||||
return null;
|
|
||||||
}).when(mFingerprintManager).addAuthenticatorsRegisteredCallback(any());
|
|
||||||
|
|
||||||
mViewModel = new FingerprintEnrollEnrollingViewModel(
|
|
||||||
mApplication,
|
|
||||||
TEST_USER_ID,
|
|
||||||
new FingerprintRepository(mFingerprintManager)
|
|
||||||
);
|
|
||||||
|
|
||||||
assertThat(mViewModel.getFirstFingerprintSensorPropertiesInternal()).isEqualTo(prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetEnrollStageCount() {
|
|
||||||
final int expectedValue = 24;
|
|
||||||
doReturn(expectedValue).when(mFingerprintManager).getEnrollStageCount();
|
|
||||||
|
|
||||||
assertThat(mViewModel.getEnrollStageCount()).isEqualTo(expectedValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetEnrollStageThreshold() {
|
|
||||||
final float expectedValue0 = 0.42f;
|
|
||||||
final float expectedValue1 = 0.24f;
|
|
||||||
final float expectedValue2 = 0.33f;
|
|
||||||
final float expectedValue3 = 0.90f;
|
|
||||||
|
|
||||||
doReturn(expectedValue0).when(mFingerprintManager).getEnrollStageThreshold(0);
|
|
||||||
doReturn(expectedValue1).when(mFingerprintManager).getEnrollStageThreshold(1);
|
|
||||||
doReturn(expectedValue2).when(mFingerprintManager).getEnrollStageThreshold(2);
|
|
||||||
doReturn(expectedValue3).when(mFingerprintManager).getEnrollStageThreshold(3);
|
|
||||||
|
|
||||||
assertThat(mViewModel.getEnrollStageThreshold(2)).isEqualTo(expectedValue2);
|
|
||||||
assertThat(mViewModel.getEnrollStageThreshold(1)).isEqualTo(expectedValue1);
|
|
||||||
assertThat(mViewModel.getEnrollStageThreshold(3)).isEqualTo(expectedValue3);
|
|
||||||
assertThat(mViewModel.getEnrollStageThreshold(0)).isEqualTo(expectedValue0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import androidx.test.core.app.ApplicationProvider
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintErrorDialogSetResultAction.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import kotlinx.coroutines.flow.toList
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
|
||||||
import kotlinx.coroutines.test.runCurrent
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class FingerprintEnrollErrorDialogViewModelTest {
|
|
||||||
|
|
||||||
private val application = ApplicationProvider.getApplicationContext<Application>()
|
|
||||||
private var viewModel: FingerprintEnrollErrorDialogViewModel =
|
|
||||||
FingerprintEnrollErrorDialogViewModel(application, false)
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
// Make sure viewModel is new for each test
|
|
||||||
viewModel = FingerprintEnrollErrorDialogViewModel(application, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testIsDialogNotShownDefaultFalse() {
|
|
||||||
assertThat(viewModel.isDialogShown).isFalse()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testIsSuw() {
|
|
||||||
assertThat(FingerprintEnrollErrorDialogViewModel(application, false).isSuw).isFalse()
|
|
||||||
assertThat(FingerprintEnrollErrorDialogViewModel(application, true).isSuw).isTrue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testNewDialog() = runTest {
|
|
||||||
val newDialogs: List<Int> = mutableListOf<Int>().also {
|
|
||||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
|
||||||
viewModel.newDialogFlow.toList(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// Default values
|
|
||||||
assertThat(viewModel.isDialogShown).isFalse()
|
|
||||||
assertThat(newDialogs.size).isEqualTo(0)
|
|
||||||
|
|
||||||
val testErrorMsgId = 3456
|
|
||||||
viewModel.newDialog(testErrorMsgId)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// verify after emit
|
|
||||||
assertThat(viewModel.isDialogShown).isTrue()
|
|
||||||
assertThat(newDialogs.size).isEqualTo(1)
|
|
||||||
assertThat(newDialogs[0]).isEqualTo(testErrorMsgId)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testTriggerRetry() = runTest {
|
|
||||||
val triggerRetries: List<Any> = mutableListOf<Any>().also {
|
|
||||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
|
||||||
viewModel.triggerRetryFlow.toList(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// Default values
|
|
||||||
assertThat(triggerRetries.size).isEqualTo(0)
|
|
||||||
|
|
||||||
viewModel.triggerRetry()
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// verify after emit
|
|
||||||
assertThat(triggerRetries.size).isEqualTo(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSetResultFinish() = runTest {
|
|
||||||
val setResults: List<FingerprintErrorDialogSetResultAction> =
|
|
||||||
mutableListOf<FingerprintErrorDialogSetResultAction>().also {
|
|
||||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
|
||||||
viewModel.setResultFlow.toList(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// Default values
|
|
||||||
assertThat(setResults.size).isEqualTo(0)
|
|
||||||
|
|
||||||
viewModel.setResultAndFinish(FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// verify after emit
|
|
||||||
assertThat(setResults.size).isEqualTo(1)
|
|
||||||
assertThat(setResults[0]).isEqualTo(FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel;
|
|
||||||
|
|
||||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG;
|
|
||||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP;
|
|
||||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import com.android.settings.testutils.InstantTaskExecutorRule;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class FingerprintEnrollFindSensorViewModelTest {
|
|
||||||
|
|
||||||
@Rule public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();
|
|
||||||
|
|
||||||
private Application mApplication;
|
|
||||||
private FingerprintEnrollFindSensorViewModel mViewModel;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
mApplication = ApplicationProvider.getApplicationContext();
|
|
||||||
mViewModel = new FingerprintEnrollFindSensorViewModel(mApplication, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testClickSkipButtonNotInSuw() {
|
|
||||||
mViewModel = new FingerprintEnrollFindSensorViewModel(mApplication, false);
|
|
||||||
mViewModel.onSkipButtonClick();
|
|
||||||
assertThat(mViewModel.getActionLiveData().getValue()).isEqualTo(
|
|
||||||
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testClickSkipButtonInSuw() {
|
|
||||||
mViewModel = new FingerprintEnrollFindSensorViewModel(mApplication, true);
|
|
||||||
mViewModel.onSkipButtonClick();
|
|
||||||
assertThat(mViewModel.getActionLiveData().getValue()).isEqualTo(
|
|
||||||
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testClickSkipDialogButton() {
|
|
||||||
mViewModel.onSkipDialogButtonClick();
|
|
||||||
assertThat(mViewModel.getActionLiveData().getValue()).isEqualTo(
|
|
||||||
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testClickStartDialogButton() {
|
|
||||||
mViewModel.onStartButtonClick();
|
|
||||||
assertThat(mViewModel.getActionLiveData().getValue()).isEqualTo(
|
|
||||||
FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testClearActionLiveData() {
|
|
||||||
assertThat(mViewModel.getActionLiveData().getValue()).isNull();
|
|
||||||
|
|
||||||
mViewModel.onStartButtonClick();
|
|
||||||
assertThat(mViewModel.getActionLiveData().getValue()).isNotNull();
|
|
||||||
|
|
||||||
mViewModel.clearActionLiveData();
|
|
||||||
assertThat(mViewModel.getActionLiveData().getValue()).isNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel;
|
|
||||||
|
|
||||||
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 com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishViewModel.FINGERPRINT_ENROLL_FINISH_ACTION_ADD_BUTTON_CLICK;
|
|
||||||
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishViewModel.FINGERPRINT_ENROLL_FINISH_ACTION_NEXT_BUTTON_CLICK;
|
|
||||||
import static com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.newFingerprintRepository;
|
|
||||||
import static com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.setupFingerprintEnrolledFingerprints;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.hardware.fingerprint.FingerprintManager;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
|
|
||||||
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 FingerprintEnrollFinishViewModelTest {
|
|
||||||
|
|
||||||
private static final int USER_ID = 334;
|
|
||||||
private static final int MAX_ENROLLABLE = 5;
|
|
||||||
|
|
||||||
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
|
||||||
@Rule public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();
|
|
||||||
|
|
||||||
@Mock private FingerprintManager mFingerprintManager;
|
|
||||||
|
|
||||||
private Application mApplication;
|
|
||||||
private EnrollmentRequest mRequest;
|
|
||||||
private FingerprintEnrollFinishViewModel mViewModel;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
mApplication = ApplicationProvider.getApplicationContext();
|
|
||||||
mRequest = new EnrollmentRequest(new Intent(), mApplication, true);
|
|
||||||
mViewModel = new FingerprintEnrollFinishViewModel(mApplication, USER_ID, mRequest,
|
|
||||||
newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, MAX_ENROLLABLE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCanAssumeSfps() {
|
|
||||||
mViewModel = new FingerprintEnrollFinishViewModel(mApplication, USER_ID, mRequest,
|
|
||||||
newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, MAX_ENROLLABLE));
|
|
||||||
assertThat(mViewModel.canAssumeSfps()).isFalse();
|
|
||||||
|
|
||||||
mViewModel = new FingerprintEnrollFinishViewModel(mApplication, USER_ID, mRequest,
|
|
||||||
newFingerprintRepository(mFingerprintManager, TYPE_REAR, MAX_ENROLLABLE));
|
|
||||||
assertThat(mViewModel.canAssumeSfps()).isFalse();
|
|
||||||
|
|
||||||
mViewModel = new FingerprintEnrollFinishViewModel(mApplication, USER_ID, mRequest,
|
|
||||||
newFingerprintRepository(mFingerprintManager, TYPE_POWER_BUTTON, MAX_ENROLLABLE));
|
|
||||||
assertThat(mViewModel.canAssumeSfps()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIsAnotherFingerprintEnrollable() {
|
|
||||||
setupFingerprintEnrolledFingerprints(mFingerprintManager, USER_ID, MAX_ENROLLABLE);
|
|
||||||
assertThat(mViewModel.isAnotherFingerprintEnrollable()).isFalse();
|
|
||||||
|
|
||||||
setupFingerprintEnrolledFingerprints(mFingerprintManager, USER_ID, MAX_ENROLLABLE - 1);
|
|
||||||
assertThat(mViewModel.isAnotherFingerprintEnrollable()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetRequest() {
|
|
||||||
assertThat(mViewModel.getRequest()).isEqualTo(mRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOnAddButtonClick() {
|
|
||||||
final LiveData<Integer> actionLiveData = mViewModel.getActionLiveData();
|
|
||||||
|
|
||||||
// Test init value
|
|
||||||
assertThat(actionLiveData.getValue()).isNull();
|
|
||||||
|
|
||||||
// Test onAddButtonClick()
|
|
||||||
mViewModel.onAddButtonClick();
|
|
||||||
assertThat(actionLiveData.getValue()).isEqualTo(
|
|
||||||
FINGERPRINT_ENROLL_FINISH_ACTION_ADD_BUTTON_CLICK);
|
|
||||||
|
|
||||||
// Clear
|
|
||||||
mViewModel.clearActionLiveData();
|
|
||||||
assertThat(actionLiveData.getValue()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOnNextButtonClick() {
|
|
||||||
final LiveData<Integer> actionLiveData = mViewModel.getActionLiveData();
|
|
||||||
|
|
||||||
// Test init value
|
|
||||||
assertThat(actionLiveData.getValue()).isNull();
|
|
||||||
|
|
||||||
// Test onNextButtonClick()
|
|
||||||
mViewModel.onNextButtonClick();
|
|
||||||
assertThat(actionLiveData.getValue()).isEqualTo(
|
|
||||||
FINGERPRINT_ENROLL_FINISH_ACTION_NEXT_BUTTON_CLICK);
|
|
||||||
|
|
||||||
// Clear
|
|
||||||
mViewModel.clearActionLiveData();
|
|
||||||
assertThat(actionLiveData.getValue()).isNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,357 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.res.Resources
|
|
||||||
import android.hardware.fingerprint.FingerprintManager
|
|
||||||
import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
|
|
||||||
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.biometrics2.ui.model.FingerprintEnrollable.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
|
||||||
import com.android.settings.biometrics2.ui.model.FingerprintEnrollable.FINGERPRINT_ENROLLABLE_OK
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction.CONTINUE_ENROLL
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction.DONE_AND_FINISH
|
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction.SKIP_OR_CANCEL
|
|
||||||
import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newAllFalseRequest
|
|
||||||
import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwDeferredRequest
|
|
||||||
import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwPortalRequest
|
|
||||||
import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwRequest
|
|
||||||
import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwSuggestedActionFlowRequest
|
|
||||||
import com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.newFingerprintRepository
|
|
||||||
import com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.setupFingerprintEnrolledFingerprints
|
|
||||||
import com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.setupSuwMaxFingerprintsEnrollable
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import kotlinx.coroutines.flow.toList
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.test.TestScope
|
|
||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
|
||||||
import kotlinx.coroutines.test.runCurrent
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.mockito.Mock
|
|
||||||
import org.mockito.Mockito
|
|
||||||
import org.mockito.junit.MockitoJUnit
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class FingerprintEnrollIntroViewModelTest {
|
|
||||||
|
|
||||||
@get:Rule val mockito = MockitoJUnit.rule()
|
|
||||||
|
|
||||||
@Mock private lateinit var resources: Resources
|
|
||||||
@Mock private lateinit var fingerprintManager: FingerprintManager
|
|
||||||
|
|
||||||
private var application: Application = ApplicationProvider.getApplicationContext()
|
|
||||||
|
|
||||||
private fun newFingerprintEnrollIntroViewModel(
|
|
||||||
fingerprintRepository: FingerprintRepository,
|
|
||||||
enrollmentRequest: EnrollmentRequest
|
|
||||||
) = FingerprintEnrollIntroViewModel(
|
|
||||||
application,
|
|
||||||
fingerprintRepository,
|
|
||||||
enrollmentRequest,
|
|
||||||
TEST_USER_ID
|
|
||||||
)
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
application = ApplicationProvider.getApplicationContext()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testPageStatusFlowDefaultAndUpdate() = runTest {
|
|
||||||
val viewModel = newFingerprintEnrollIntroViewModel(
|
|
||||||
newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 1),
|
|
||||||
newAllFalseRequest(application)
|
|
||||||
)
|
|
||||||
|
|
||||||
val statusList = listOfPageStatusFlow(viewModel)
|
|
||||||
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// assert default values
|
|
||||||
assertThat(statusList.size).isEqualTo(1)
|
|
||||||
assertThat(statusList[0].hasScrollToBottom()).isFalse()
|
|
||||||
assertThat(statusList[0].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_OK)
|
|
||||||
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 1)
|
|
||||||
viewModel.updateEnrollableStatus(backgroundScope)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
// assert new updated value
|
|
||||||
assertThat(statusList.size).isEqualTo(2)
|
|
||||||
assertThat(statusList[1].hasScrollToBottom()).isFalse()
|
|
||||||
assertThat(statusList[1].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testOnStartToUpdateEnrollableStatusOk_isSuw() = runTest {
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 0)
|
|
||||||
setupSuwMaxFingerprintsEnrollable(application, resources, 1)
|
|
||||||
val viewModel = newFingerprintEnrollIntroViewModel(
|
|
||||||
newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
|
|
||||||
newIsSuwRequest(application)
|
|
||||||
)
|
|
||||||
|
|
||||||
val statusList = listOfPageStatusFlow(viewModel)
|
|
||||||
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(statusList.size).isEqualTo(1)
|
|
||||||
assertThat(statusList[0].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_OK)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOnStartToUpdateEnrollableStatusReachMax_isSuw() = runTest {
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 1)
|
|
||||||
setupSuwMaxFingerprintsEnrollable(application, resources, 1)
|
|
||||||
val viewModel = newFingerprintEnrollIntroViewModel(
|
|
||||||
newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
|
|
||||||
newIsSuwRequest(application)
|
|
||||||
)
|
|
||||||
|
|
||||||
val statusList = listOfPageStatusFlow(viewModel)
|
|
||||||
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(statusList.size).isEqualTo(1)
|
|
||||||
assertThat(statusList[0].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOnStartToUpdateEnrollableStatusOk_isNotSuw() = runTest {
|
|
||||||
testOnStartToUpdateEnrollableStatusOk(newAllFalseRequest(application))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOnStartToUpdateEnrollableStatusReachMax_isNotSuw() = runTest {
|
|
||||||
testOnStartToUpdateEnrollableStatusReachMax(newAllFalseRequest(application))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOnStartToUpdateEnrollableStatusOk_isSuwDeferred() = runTest {
|
|
||||||
testOnStartToUpdateEnrollableStatusOk(newIsSuwDeferredRequest(application))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOnStartToUpdateEnrollableStatusReachMax_isSuwDeferred() = runTest {
|
|
||||||
testOnStartToUpdateEnrollableStatusReachMax(newIsSuwDeferredRequest(application))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOnStartToUpdateEnrollableStatusOk_isSuwPortal() = runTest {
|
|
||||||
testOnStartToUpdateEnrollableStatusOk(newIsSuwPortalRequest(application))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOnStartToUpdateEnrollableStatusReachMax_isSuwPortal() = runTest {
|
|
||||||
testOnStartToUpdateEnrollableStatusReachMax(newIsSuwPortalRequest(application))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOnStartToUpdateEnrollableStatusOk_isSuwSuggestedActionFlow() = runTest {
|
|
||||||
testOnStartToUpdateEnrollableStatusOk(newIsSuwSuggestedActionFlowRequest(application))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOnStartToUpdateEnrollableStatusReachMax_isSuwSuggestedActionFlow() = runTest {
|
|
||||||
testOnStartToUpdateEnrollableStatusReachMax(
|
|
||||||
newIsSuwSuggestedActionFlowRequest(application)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun TestScope.testOnStartToUpdateEnrollableStatusOk(request: EnrollmentRequest) {
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 0)
|
|
||||||
val viewModel = newFingerprintEnrollIntroViewModel(
|
|
||||||
newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
|
|
||||||
request
|
|
||||||
)
|
|
||||||
|
|
||||||
val statusList = listOfPageStatusFlow(viewModel)
|
|
||||||
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(statusList.size).isEqualTo(1)
|
|
||||||
assertThat(statusList[0].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_OK)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun TestScope.testOnStartToUpdateEnrollableStatusReachMax(request: EnrollmentRequest) {
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 5)
|
|
||||||
val viewModel = newFingerprintEnrollIntroViewModel(
|
|
||||||
newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
|
|
||||||
request
|
|
||||||
)
|
|
||||||
|
|
||||||
val statusList = listOfPageStatusFlow(viewModel)
|
|
||||||
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(statusList.size).isEqualTo(1)
|
|
||||||
assertThat(statusList[0].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testIsParentalConsentRequired() {
|
|
||||||
// We shall not mock FingerprintRepository, but
|
|
||||||
// FingerprintRepository.isParentalConsentRequired() calls static method inside, we can't
|
|
||||||
// mock static method
|
|
||||||
val fingerprintRepository = Mockito.mock(
|
|
||||||
FingerprintRepository::class.java
|
|
||||||
)
|
|
||||||
val viewModel = FingerprintEnrollIntroViewModel(
|
|
||||||
application,
|
|
||||||
fingerprintRepository,
|
|
||||||
newAllFalseRequest(application),
|
|
||||||
TEST_USER_ID
|
|
||||||
)
|
|
||||||
Mockito.`when`(
|
|
||||||
fingerprintRepository.isParentalConsentRequired(application)
|
|
||||||
).thenReturn(true)
|
|
||||||
assertThat(viewModel.isParentalConsentRequired).isEqualTo(true)
|
|
||||||
Mockito.`when`(
|
|
||||||
fingerprintRepository.isParentalConsentRequired(application)
|
|
||||||
).thenReturn(false)
|
|
||||||
assertThat(viewModel.isParentalConsentRequired).isEqualTo(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testIsBiometricUnlockDisabledByAdmin() {
|
|
||||||
// We shall not mock FingerprintRepository, but
|
|
||||||
// FingerprintRepository.isDisabledByAdmin() calls static method inside, we can't mock
|
|
||||||
// static method
|
|
||||||
val fingerprintRepository = Mockito.mock(FingerprintRepository::class.java)
|
|
||||||
val viewModel = FingerprintEnrollIntroViewModel(
|
|
||||||
application,
|
|
||||||
fingerprintRepository,
|
|
||||||
newAllFalseRequest(application),
|
|
||||||
TEST_USER_ID
|
|
||||||
)
|
|
||||||
Mockito.`when`(
|
|
||||||
fingerprintRepository.isDisabledByAdmin(application, TEST_USER_ID)
|
|
||||||
).thenReturn(true)
|
|
||||||
assertThat(viewModel.isBiometricUnlockDisabledByAdmin).isEqualTo(true)
|
|
||||||
Mockito.`when`(
|
|
||||||
fingerprintRepository.isDisabledByAdmin(application, TEST_USER_ID)
|
|
||||||
).thenReturn(false)
|
|
||||||
assertThat(viewModel.isBiometricUnlockDisabledByAdmin).isEqualTo(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSetHasScrolledToBottom() = runTest {
|
|
||||||
val viewModel = newFingerprintEnrollIntroViewModel(
|
|
||||||
newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
|
|
||||||
newAllFalseRequest(application)
|
|
||||||
)
|
|
||||||
|
|
||||||
val pageStatusList = listOfPageStatusFlow(viewModel)
|
|
||||||
|
|
||||||
viewModel.setHasScrolledToBottom(true, backgroundScope)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(pageStatusList[pageStatusList.size-1].hasScrollToBottom()).isEqualTo(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOnNextButtonClick_enrollNext() = runTest {
|
|
||||||
// Set latest status to FINGERPRINT_ENROLLABLE_OK
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 0)
|
|
||||||
setupSuwMaxFingerprintsEnrollable(application, resources, 1)
|
|
||||||
val viewModel = newFingerprintEnrollIntroViewModel(
|
|
||||||
newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
|
|
||||||
newIsSuwRequest(application)
|
|
||||||
)
|
|
||||||
|
|
||||||
val actions = listOfActionFlow(viewModel)
|
|
||||||
|
|
||||||
// Perform click on `next`
|
|
||||||
viewModel.onNextButtonClick(backgroundScope)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(actions.size).isEqualTo(1)
|
|
||||||
assertThat(actions[0]).isEqualTo(CONTINUE_ENROLL)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOnNextButtonClick_doneAndFinish() = runTest {
|
|
||||||
// Set latest status to FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 1)
|
|
||||||
setupSuwMaxFingerprintsEnrollable(application, resources, 1)
|
|
||||||
val viewModel = newFingerprintEnrollIntroViewModel(
|
|
||||||
newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
|
|
||||||
newIsSuwRequest(application)
|
|
||||||
)
|
|
||||||
|
|
||||||
val statusList = listOfPageStatusFlow(viewModel)
|
|
||||||
val actionList = listOfActionFlow(viewModel)
|
|
||||||
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(statusList.size).isEqualTo(1)
|
|
||||||
assertThat(statusList[0].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX)
|
|
||||||
|
|
||||||
val actions = listOfActionFlow(viewModel)
|
|
||||||
|
|
||||||
// Perform click on `next`
|
|
||||||
viewModel.onNextButtonClick(backgroundScope)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(actionList.size).isEqualTo(1)
|
|
||||||
assertThat(actionList[0]).isEqualTo(DONE_AND_FINISH)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOnSkipOrCancelButtonClick() = runTest {
|
|
||||||
val viewModel = newFingerprintEnrollIntroViewModel(
|
|
||||||
newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
|
|
||||||
newAllFalseRequest(application)
|
|
||||||
)
|
|
||||||
|
|
||||||
val actions = listOfActionFlow(viewModel)
|
|
||||||
|
|
||||||
viewModel.onSkipOrCancelButtonClick(backgroundScope)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(actions.size).isEqualTo(1)
|
|
||||||
assertThat(actions[0]).isEqualTo(SKIP_OR_CANCEL)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun TestScope.listOfActionFlow(
|
|
||||||
viewModel: FingerprintEnrollIntroViewModel
|
|
||||||
): List<FingerprintEnrollIntroAction> =
|
|
||||||
mutableListOf<FingerprintEnrollIntroAction>().also {
|
|
||||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
|
||||||
viewModel.actionFlow.toList(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun TestScope.listOfPageStatusFlow(
|
|
||||||
viewModel: FingerprintEnrollIntroViewModel
|
|
||||||
): List<FingerprintEnrollIntroStatus> =
|
|
||||||
mutableListOf<FingerprintEnrollIntroStatus>().also {
|
|
||||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
|
||||||
viewModel.pageStatusFlow.toList(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TEST_USER_ID = 33
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,421 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel;
|
|
||||||
|
|
||||||
import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL;
|
|
||||||
import static android.hardware.fingerprint.FingerprintManager.ENROLL_FIND_SENSOR;
|
|
||||||
import static android.hardware.fingerprint.FingerprintManager.EnrollReason;
|
|
||||||
import static android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
|
|
||||||
|
|
||||||
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.Mockito.any;
|
|
||||||
import static org.mockito.Mockito.doAnswer;
|
|
||||||
import static org.mockito.Mockito.eq;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.only;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.os.CancellationSignal;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.os.Message;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintUpdater;
|
|
||||||
import com.android.settings.biometrics.fingerprint.MessageDisplayController;
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage;
|
|
||||||
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 FingerprintEnrollProgressViewModelTest {
|
|
||||||
|
|
||||||
private static final int TEST_USER_ID = 334;
|
|
||||||
|
|
||||||
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
|
||||||
@Rule public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();
|
|
||||||
|
|
||||||
@Mock private Application mApplication;
|
|
||||||
@Mock private Resources mResources;
|
|
||||||
@Mock private FingerprintUpdater mFingerprintUpdater;
|
|
||||||
|
|
||||||
private FingerprintEnrollProgressViewModel mViewModel;
|
|
||||||
private final TestWrapper<CancellationSignal> mCancellationSignalWrapper = new TestWrapper<>();
|
|
||||||
private final TestWrapper<EnrollmentCallback> mCallbackWrapper = new TestWrapper<>();
|
|
||||||
private int mEnrollmentMessageDisplayControllerFlagResId;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
mEnrollmentMessageDisplayControllerFlagResId = ApplicationProvider.getApplicationContext()
|
|
||||||
.getResources().getIdentifier("enrollment_message_display_controller_flag", "bool",
|
|
||||||
SETTINGS_PACKAGE_NAME);
|
|
||||||
|
|
||||||
when(mApplication.getResources()).thenReturn(mResources);
|
|
||||||
|
|
||||||
// Not use MessageDisplayController by default
|
|
||||||
when(mResources.getBoolean(mEnrollmentMessageDisplayControllerFlagResId)).thenReturn(false);
|
|
||||||
mViewModel = new FingerprintEnrollProgressViewModel(mApplication, mFingerprintUpdater,
|
|
||||||
TEST_USER_ID);
|
|
||||||
|
|
||||||
mCancellationSignalWrapper.mValue = null;
|
|
||||||
mCallbackWrapper.mValue = null;
|
|
||||||
doAnswer(invocation -> {
|
|
||||||
mCancellationSignalWrapper.mValue = invocation.getArgument(1);
|
|
||||||
mCallbackWrapper.mValue = invocation.getArgument(3);
|
|
||||||
return null;
|
|
||||||
}).when(mFingerprintUpdater).enroll(any(byte[].class), any(CancellationSignal.class),
|
|
||||||
eq(TEST_USER_ID), any(EnrollmentCallback.class), anyInt(), any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStartFindSensor() {
|
|
||||||
@EnrollReason final int enrollReason = ENROLL_FIND_SENSOR;
|
|
||||||
final byte[] token = new byte[] { 1, 2, 3 };
|
|
||||||
mViewModel.setToken(token);
|
|
||||||
|
|
||||||
// Start enrollment
|
|
||||||
final Object ret = mViewModel.startEnrollment(enrollReason);
|
|
||||||
|
|
||||||
assertThat(ret).isNotNull();
|
|
||||||
verify(mFingerprintUpdater, only()).enroll(eq(token), any(CancellationSignal.class),
|
|
||||||
eq(TEST_USER_ID), any(EnrollmentCallback.class), eq(enrollReason), any());
|
|
||||||
assertThat(mCallbackWrapper.mValue instanceof MessageDisplayController).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStartEnrolling() {
|
|
||||||
@EnrollReason final int enrollReason = ENROLL_ENROLL;
|
|
||||||
final byte[] token = new byte[] { 1, 2, 3 };
|
|
||||||
mViewModel.setToken(token);
|
|
||||||
|
|
||||||
// Start enrollment
|
|
||||||
final Object ret = mViewModel.startEnrollment(enrollReason);
|
|
||||||
|
|
||||||
assertThat(ret).isNotNull();
|
|
||||||
verify(mFingerprintUpdater, only()).enroll(eq(token), any(CancellationSignal.class),
|
|
||||||
eq(TEST_USER_ID), any(EnrollmentCallback.class), eq(enrollReason), any());
|
|
||||||
assertThat(mCallbackWrapper.mValue instanceof MessageDisplayController).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStartEnrollingWithMessageDisplayController() {
|
|
||||||
// Enable MessageDisplayController and mock handler for it
|
|
||||||
when(mResources.getBoolean(mEnrollmentMessageDisplayControllerFlagResId)).thenReturn(true);
|
|
||||||
when(mApplication.getMainThreadHandler()).thenReturn(new TestHandler());
|
|
||||||
|
|
||||||
@EnrollReason final int enrollReason = ENROLL_ENROLL;
|
|
||||||
final byte[] token = new byte[] { 1, 2, 3 };
|
|
||||||
mViewModel.setToken(token);
|
|
||||||
|
|
||||||
// Start enrollment
|
|
||||||
final Object ret = mViewModel.startEnrollment(enrollReason);
|
|
||||||
|
|
||||||
assertThat(ret).isNotNull();
|
|
||||||
verify(mFingerprintUpdater, only()).enroll(eq(token), any(CancellationSignal.class),
|
|
||||||
eq(TEST_USER_ID), any(MessageDisplayController.class), eq(enrollReason), any());
|
|
||||||
assertThat(mCallbackWrapper.mValue).isNotNull();
|
|
||||||
|
|
||||||
assertThat(mCallbackWrapper.mValue instanceof MessageDisplayController).isTrue();
|
|
||||||
final EnrollmentCallback callback1 = mCallbackWrapper.mValue;
|
|
||||||
|
|
||||||
// Cancel and start again
|
|
||||||
mViewModel.cancelEnrollment();
|
|
||||||
mViewModel.startEnrollment(enrollReason);
|
|
||||||
|
|
||||||
// Shall not use the same MessageDisplayController
|
|
||||||
verify(mFingerprintUpdater, times(2)).enroll(eq(token), any(CancellationSignal.class),
|
|
||||||
eq(TEST_USER_ID), any(MessageDisplayController.class), eq(enrollReason), any());
|
|
||||||
assertThat(mCallbackWrapper.mValue).isNotNull();
|
|
||||||
assertThat(callback1).isNotEqualTo(mCallbackWrapper.mValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStartEnrollmentFailBecauseOfNoToken() {
|
|
||||||
// Start enrollment
|
|
||||||
final Object ret = mViewModel.startEnrollment(ENROLL_FIND_SENSOR);
|
|
||||||
|
|
||||||
assertThat(ret).isNull();
|
|
||||||
verify(mFingerprintUpdater, never()).enroll(any(byte[].class),
|
|
||||||
any(CancellationSignal.class), anyInt(), any(EnrollmentCallback.class), anyInt(),
|
|
||||||
any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCancelEnrollment() {
|
|
||||||
// Start enrollment
|
|
||||||
mViewModel.setToken(new byte[] { 1, 2, 3 });
|
|
||||||
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
|
|
||||||
assertThat(ret).isNotNull();
|
|
||||||
assertThat(mCancellationSignalWrapper.mValue).isNotNull();
|
|
||||||
|
|
||||||
// Cancel enrollment
|
|
||||||
mViewModel.cancelEnrollment();
|
|
||||||
|
|
||||||
assertThat(mCancellationSignalWrapper.mValue.isCanceled()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testProgressUpdate() {
|
|
||||||
// Start enrollment
|
|
||||||
mViewModel.setToken(new byte[] { 1, 2, 3 });
|
|
||||||
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
|
|
||||||
assertThat(ret).isNotNull();
|
|
||||||
assertThat(mCallbackWrapper.mValue).isNotNull();
|
|
||||||
|
|
||||||
// Test default value
|
|
||||||
final LiveData<EnrollmentProgress> progressLiveData = mViewModel.getProgressLiveData();
|
|
||||||
EnrollmentProgress progress = progressLiveData.getValue();
|
|
||||||
assertThat(progress).isNotNull();
|
|
||||||
assertThat(progress.getSteps()).isEqualTo(-1);
|
|
||||||
assertThat(progress.getRemaining()).isEqualTo(0);
|
|
||||||
|
|
||||||
// Update first progress
|
|
||||||
mCallbackWrapper.mValue.onEnrollmentProgress(25);
|
|
||||||
progress = progressLiveData.getValue();
|
|
||||||
assertThat(progress).isNotNull();
|
|
||||||
assertThat(progress.getSteps()).isEqualTo(25);
|
|
||||||
assertThat(progress.getRemaining()).isEqualTo(25);
|
|
||||||
|
|
||||||
// Update second progress
|
|
||||||
mCallbackWrapper.mValue.onEnrollmentProgress(20);
|
|
||||||
progress = progressLiveData.getValue();
|
|
||||||
assertThat(progress).isNotNull();
|
|
||||||
assertThat(progress.getSteps()).isEqualTo(25);
|
|
||||||
assertThat(progress.getRemaining()).isEqualTo(20);
|
|
||||||
|
|
||||||
// Update latest progress
|
|
||||||
mCallbackWrapper.mValue.onEnrollmentProgress(0);
|
|
||||||
progress = progressLiveData.getValue();
|
|
||||||
assertThat(progress).isNotNull();
|
|
||||||
assertThat(progress.getSteps()).isEqualTo(25);
|
|
||||||
assertThat(progress.getRemaining()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testProgressUpdateClearHelpMessage() {
|
|
||||||
// Start enrollment
|
|
||||||
mViewModel.setToken(new byte[] { 1, 2, 3 });
|
|
||||||
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
|
|
||||||
assertThat(ret).isNotNull();
|
|
||||||
assertThat(mCallbackWrapper.mValue).isNotNull();
|
|
||||||
final LiveData<EnrollmentProgress> progressLiveData = mViewModel.getProgressLiveData();
|
|
||||||
final LiveData<EnrollmentStatusMessage> helpMsgLiveData =
|
|
||||||
mViewModel.getHelpMessageLiveData();
|
|
||||||
|
|
||||||
// Update first progress
|
|
||||||
mCallbackWrapper.mValue.onEnrollmentProgress(25);
|
|
||||||
EnrollmentProgress progress = progressLiveData.getValue();
|
|
||||||
assertThat(progress).isNotNull();
|
|
||||||
assertThat(progress.getSteps()).isEqualTo(25);
|
|
||||||
assertThat(progress.getRemaining()).isEqualTo(25);
|
|
||||||
|
|
||||||
// Update help message
|
|
||||||
final int testHelpMsgId = 3;
|
|
||||||
final String testHelpString = "Test Help String";
|
|
||||||
mCallbackWrapper.mValue.onEnrollmentHelp(testHelpMsgId, testHelpString);
|
|
||||||
final EnrollmentStatusMessage helpMsg = helpMsgLiveData.getValue();
|
|
||||||
assertThat(helpMsg).isNotNull();
|
|
||||||
assertThat(helpMsg.getMsgId()).isEqualTo(testHelpMsgId);
|
|
||||||
assertThat(helpMsg.getStr().toString()).isEqualTo(testHelpString);
|
|
||||||
|
|
||||||
// Update second progress
|
|
||||||
mCallbackWrapper.mValue.onEnrollmentProgress(20);
|
|
||||||
progress = progressLiveData.getValue();
|
|
||||||
assertThat(progress).isNotNull();
|
|
||||||
assertThat(progress.getSteps()).isEqualTo(25);
|
|
||||||
assertThat(progress.getRemaining()).isEqualTo(20);
|
|
||||||
|
|
||||||
// Help message shall be set to null
|
|
||||||
assertThat(helpMsgLiveData.getValue()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testProgressUpdateWithMessageDisplayController() {
|
|
||||||
// Enable MessageDisplayController and mock handler for it
|
|
||||||
when(mResources.getBoolean(mEnrollmentMessageDisplayControllerFlagResId)).thenReturn(true);
|
|
||||||
when(mApplication.getMainThreadHandler()).thenReturn(new TestHandler());
|
|
||||||
|
|
||||||
mViewModel.setToken(new byte[] { 1, 2, 3 });
|
|
||||||
|
|
||||||
// Start enrollment
|
|
||||||
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
|
|
||||||
assertThat(ret).isNotNull();
|
|
||||||
assertThat(mCallbackWrapper.mValue).isNotNull();
|
|
||||||
|
|
||||||
// Test default value
|
|
||||||
final LiveData<EnrollmentProgress> progressLiveData = mViewModel.getProgressLiveData();
|
|
||||||
EnrollmentProgress progress = progressLiveData.getValue();
|
|
||||||
assertThat(progress).isNotNull();
|
|
||||||
assertThat(progress.getSteps()).isEqualTo(-1);
|
|
||||||
assertThat(progress.getRemaining()).isEqualTo(0);
|
|
||||||
|
|
||||||
// Update first progress
|
|
||||||
mCallbackWrapper.mValue.onEnrollmentProgress(25);
|
|
||||||
progress = progressLiveData.getValue();
|
|
||||||
assertThat(progress).isNotNull();
|
|
||||||
assertThat(progress.getSteps()).isEqualTo(25);
|
|
||||||
assertThat(progress.getRemaining()).isEqualTo(25);
|
|
||||||
|
|
||||||
// Update second progress
|
|
||||||
mCallbackWrapper.mValue.onEnrollmentProgress(20);
|
|
||||||
progress = progressLiveData.getValue();
|
|
||||||
assertThat(progress).isNotNull();
|
|
||||||
assertThat(progress.getSteps()).isEqualTo(25);
|
|
||||||
assertThat(progress.getRemaining()).isEqualTo(20);
|
|
||||||
|
|
||||||
// Update latest progress
|
|
||||||
mCallbackWrapper.mValue.onEnrollmentProgress(0);
|
|
||||||
progress = progressLiveData.getValue();
|
|
||||||
assertThat(progress).isNotNull();
|
|
||||||
assertThat(progress.getSteps()).isEqualTo(25);
|
|
||||||
assertThat(progress.getRemaining()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetErrorMessageLiveData() {
|
|
||||||
// Start enrollment
|
|
||||||
mViewModel.setToken(new byte[] { 1, 2, 3 });
|
|
||||||
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
|
|
||||||
assertThat(ret).isNotNull();
|
|
||||||
assertThat(mCallbackWrapper.mValue).isNotNull();
|
|
||||||
|
|
||||||
// Check default value
|
|
||||||
final LiveData<EnrollmentStatusMessage> liveData = mViewModel.getErrorMessageLiveData();
|
|
||||||
assertThat(liveData.getValue()).isNull();
|
|
||||||
|
|
||||||
// Notify error message
|
|
||||||
final int errMsgId = 3;
|
|
||||||
final String errMsg = "test error message";
|
|
||||||
mCallbackWrapper.mValue.onEnrollmentError(errMsgId, errMsg);
|
|
||||||
final EnrollmentStatusMessage value = liveData.getValue();
|
|
||||||
assertThat(value).isNotNull();
|
|
||||||
assertThat(value.getMsgId()).isEqualTo(errMsgId);
|
|
||||||
assertThat(value.getStr().toString()).isEqualTo(errMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetHelpMessageLiveData() {
|
|
||||||
// Start enrollment
|
|
||||||
mViewModel.setToken(new byte[] { 1, 2, 3 });
|
|
||||||
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
|
|
||||||
assertThat(ret).isNotNull();
|
|
||||||
assertThat(mCallbackWrapper.mValue).isNotNull();
|
|
||||||
|
|
||||||
// Check default value
|
|
||||||
final LiveData<EnrollmentStatusMessage> liveData = mViewModel.getHelpMessageLiveData();
|
|
||||||
assertThat(liveData.getValue()).isNull();
|
|
||||||
|
|
||||||
// Notify help message
|
|
||||||
final int errMsgId = 3;
|
|
||||||
final String errMsg = "test error message";
|
|
||||||
mCallbackWrapper.mValue.onEnrollmentHelp(errMsgId, errMsg);
|
|
||||||
final EnrollmentStatusMessage value = liveData.getValue();
|
|
||||||
assertThat(value).isNotNull();
|
|
||||||
assertThat(value.getMsgId()).isEqualTo(errMsgId);
|
|
||||||
assertThat(value.getStr().toString()).isEqualTo(errMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetAcquireLiveData() {
|
|
||||||
// Start enrollment
|
|
||||||
mViewModel.setToken(new byte[] { 1, 2, 3 });
|
|
||||||
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
|
|
||||||
assertThat(ret).isNotNull();
|
|
||||||
assertThat(mCallbackWrapper.mValue).isNotNull();
|
|
||||||
|
|
||||||
// Check default value
|
|
||||||
final LiveData<Boolean> liveData = mViewModel.getAcquireLiveData();
|
|
||||||
assertThat(liveData.getValue()).isNull();
|
|
||||||
|
|
||||||
// Notify acquire message
|
|
||||||
mCallbackWrapper.mValue.onAcquired(true);
|
|
||||||
assertThat(liveData.getValue()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetPointerDownLiveData() {
|
|
||||||
// Start enrollment
|
|
||||||
mViewModel.setToken(new byte[] { 1, 2, 3 });
|
|
||||||
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
|
|
||||||
assertThat(ret).isNotNull();
|
|
||||||
assertThat(mCallbackWrapper.mValue).isNotNull();
|
|
||||||
|
|
||||||
// Check default value
|
|
||||||
final LiveData<Integer> liveData = mViewModel.getPointerDownLiveData();
|
|
||||||
assertThat(liveData.getValue()).isNull();
|
|
||||||
|
|
||||||
// Notify acquire message
|
|
||||||
final int value = 33;
|
|
||||||
mCallbackWrapper.mValue.onUdfpsPointerDown(value);
|
|
||||||
assertThat(liveData.getValue()).isEqualTo(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetPointerUpLiveData() {
|
|
||||||
// Start enrollment
|
|
||||||
mViewModel.setToken(new byte[] { 1, 2, 3 });
|
|
||||||
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
|
|
||||||
assertThat(ret).isNotNull();
|
|
||||||
assertThat(mCallbackWrapper.mValue).isNotNull();
|
|
||||||
|
|
||||||
// Check default value
|
|
||||||
final LiveData<Integer> liveData = mViewModel.getPointerUpLiveData();
|
|
||||||
assertThat(liveData.getValue()).isNull();
|
|
||||||
|
|
||||||
// Notify acquire message
|
|
||||||
final int value = 44;
|
|
||||||
mCallbackWrapper.mValue.onUdfpsPointerUp(value);
|
|
||||||
assertThat(liveData.getValue()).isEqualTo(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class TestWrapper<T> {
|
|
||||||
T mValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class TestHandler extends Handler {
|
|
||||||
|
|
||||||
TestHandler() {
|
|
||||||
super(Looper.getMainLooper());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
|
|
||||||
msg.getCallback().run();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,322 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.ui.viewmodel
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Intent
|
|
||||||
import android.hardware.fingerprint.FingerprintManager
|
|
||||||
import android.hardware.fingerprint.FingerprintSensorProperties
|
|
||||||
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.biometrics.BiometricEnrollBase
|
|
||||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository
|
|
||||||
import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newAllFalseRequest
|
|
||||||
import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwRequest
|
|
||||||
import com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.newFingerprintRepository
|
|
||||||
import com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.setupFingerprintEnrolledFingerprints
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import kotlinx.coroutines.flow.toList
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.test.TestScope
|
|
||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
|
||||||
import kotlinx.coroutines.test.runCurrent
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
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)
|
|
||||||
class FingerprintEnrollmentViewModelTest {
|
|
||||||
|
|
||||||
@get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
|
|
||||||
|
|
||||||
private val application: Application
|
|
||||||
get() = ApplicationProvider.getApplicationContext()
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private lateinit var fingerprintManager: FingerprintManager
|
|
||||||
|
|
||||||
private lateinit var fingerprintRepository: FingerprintRepository
|
|
||||||
private lateinit var viewModel: FingerprintEnrollmentViewModel
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
fingerprintRepository = newFingerprintRepository(
|
|
||||||
fingerprintManager,
|
|
||||||
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
|
|
||||||
5
|
|
||||||
)
|
|
||||||
viewModel = FingerprintEnrollmentViewModel(
|
|
||||||
application,
|
|
||||||
fingerprintRepository,
|
|
||||||
newAllFalseRequest(application)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGetRequest() {
|
|
||||||
assertThat(viewModel.request).isNotNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testIsWaitingActivityResultDefaultFalse() {
|
|
||||||
assertThat(viewModel.isWaitingActivityResult.value).isFalse()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOverrideActivityResult_shallKeepNullIntent_woChallengeExtra() {
|
|
||||||
val retResult = viewModel.getOverrideActivityResult(
|
|
||||||
ActivityResult(22, null), null
|
|
||||||
)
|
|
||||||
assertThat(retResult).isNotNull()
|
|
||||||
assertThat(retResult.data).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOverrideActivityResult_shallKeepNullIntent_noIntent_woChallengeExtra() {
|
|
||||||
val intent = Intent()
|
|
||||||
val retResult = viewModel.getOverrideActivityResult(
|
|
||||||
ActivityResult(33, intent), null
|
|
||||||
)
|
|
||||||
assertThat(retResult).isNotNull()
|
|
||||||
assertThat(retResult.data).isEqualTo(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOverrideActivityResult_shallKeepNull_woAdded_woIntent_withChallenge() {
|
|
||||||
val extra = Bundle()
|
|
||||||
extra.putString("test1", "test123")
|
|
||||||
|
|
||||||
val retResult = viewModel.getOverrideActivityResult(
|
|
||||||
ActivityResult(33, null), extra
|
|
||||||
)
|
|
||||||
|
|
||||||
assertThat(retResult).isNotNull()
|
|
||||||
assertThat(retResult.data).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOverrideActivityResult_shallCreateNew_woIntent_withChallenge() {
|
|
||||||
val key1 = "test1"
|
|
||||||
val key2 = "test2"
|
|
||||||
val extra = Bundle().apply {
|
|
||||||
putString(key1, "test123")
|
|
||||||
putInt(key2, 9999)
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.isNewFingerprintAdded = true
|
|
||||||
|
|
||||||
val retResult = viewModel.getOverrideActivityResult(
|
|
||||||
ActivityResult(33, null), extra
|
|
||||||
)
|
|
||||||
assertThat(retResult).isNotNull()
|
|
||||||
|
|
||||||
val retIntent = retResult.data
|
|
||||||
assertThat(retIntent).isNotNull()
|
|
||||||
|
|
||||||
val retExtra = retIntent!!.extras
|
|
||||||
assertThat(retExtra).isNotNull()
|
|
||||||
assertThat(retExtra!!.size).isEqualTo(extra.size)
|
|
||||||
assertThat(retExtra.getString(key1)).isEqualTo(extra.getString(key1))
|
|
||||||
assertThat(retExtra.getInt(key2)).isEqualTo(extra.getInt(key2))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOverrideActivityResult_shallNotMerge_nonAdded_woIntent_withChallenge() {
|
|
||||||
val extra = Bundle().apply {
|
|
||||||
putString("test2", "test123")
|
|
||||||
}
|
|
||||||
|
|
||||||
val key2 = "test2"
|
|
||||||
val intent = Intent().apply {
|
|
||||||
putExtra(key2, 3456L)
|
|
||||||
}
|
|
||||||
|
|
||||||
val retResult = viewModel.getOverrideActivityResult(ActivityResult(33, intent), extra)
|
|
||||||
|
|
||||||
assertThat(retResult).isNotNull()
|
|
||||||
|
|
||||||
val retIntent = retResult.data
|
|
||||||
assertThat(retIntent).isNotNull()
|
|
||||||
|
|
||||||
val retExtra = retIntent!!.extras
|
|
||||||
assertThat(retExtra).isNotNull()
|
|
||||||
assertThat(retExtra!!.size).isEqualTo(intent.extras!!.size)
|
|
||||||
assertThat(retExtra.getString(key2)).isEqualTo(intent.extras!!.getString(key2))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testOverrideActivityResult_shallMerge_added_woIntent_withChallenge() {
|
|
||||||
val key1 = "test1"
|
|
||||||
val key2 = "test2"
|
|
||||||
val extra = Bundle().apply {
|
|
||||||
putString(key1, "test123")
|
|
||||||
putInt(key2, 9999)
|
|
||||||
}
|
|
||||||
|
|
||||||
val key3 = "test3"
|
|
||||||
val intent = Intent().apply {
|
|
||||||
putExtra(key3, 3456L)
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.isNewFingerprintAdded = true
|
|
||||||
|
|
||||||
val retResult = viewModel.getOverrideActivityResult(ActivityResult(33, intent), extra)
|
|
||||||
assertThat(retResult).isNotNull()
|
|
||||||
|
|
||||||
val retIntent = retResult.data
|
|
||||||
assertThat(retIntent).isNotNull()
|
|
||||||
|
|
||||||
val retExtra = retIntent!!.extras
|
|
||||||
assertThat(retExtra).isNotNull()
|
|
||||||
assertThat(retExtra!!.size).isEqualTo(extra.size + intent.extras!!.size)
|
|
||||||
assertThat(retExtra.getString(key1)).isEqualTo(extra.getString(key1))
|
|
||||||
assertThat(retExtra.getInt(key2)).isEqualTo(extra.getInt(key2))
|
|
||||||
assertThat(retExtra.getLong(key3)).isEqualTo(intent.extras!!.getLong(key3))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testIsMaxEnrolledReached() {
|
|
||||||
val uid = 100
|
|
||||||
fingerprintRepository = newFingerprintRepository(
|
|
||||||
fingerprintManager,
|
|
||||||
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
|
|
||||||
3
|
|
||||||
)
|
|
||||||
viewModel = FingerprintEnrollmentViewModel(
|
|
||||||
application,
|
|
||||||
fingerprintRepository,
|
|
||||||
newAllFalseRequest(application)
|
|
||||||
)
|
|
||||||
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 0)
|
|
||||||
assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
|
|
||||||
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 1)
|
|
||||||
assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
|
|
||||||
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 2)
|
|
||||||
assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
|
|
||||||
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 3)
|
|
||||||
assertThat(viewModel.isMaxEnrolledReached(uid)).isTrue()
|
|
||||||
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 4)
|
|
||||||
assertThat(viewModel.isMaxEnrolledReached(uid)).isTrue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSetResultFlow_defaultEmpty() = runTest {
|
|
||||||
val activityResults = listOfSetResultFlow()
|
|
||||||
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(activityResults.size).isEqualTo(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCheckFinishActivityDuringOnPause_doNothingIfIsSuw() = runTest {
|
|
||||||
viewModel = FingerprintEnrollmentViewModel(
|
|
||||||
application,
|
|
||||||
fingerprintRepository,
|
|
||||||
newIsSuwRequest(application)
|
|
||||||
)
|
|
||||||
|
|
||||||
val activityResults = listOfSetResultFlow()
|
|
||||||
|
|
||||||
viewModel.checkFinishActivityDuringOnPause(
|
|
||||||
isActivityFinishing = false,
|
|
||||||
isChangingConfigurations = false,
|
|
||||||
scope = this
|
|
||||||
)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(activityResults.size).isEqualTo(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCheckFinishActivityDuringOnPause_doNothingIfIsWaitingActivity() = runTest {
|
|
||||||
val activityResults = listOfSetResultFlow()
|
|
||||||
|
|
||||||
viewModel.isWaitingActivityResult.value = true
|
|
||||||
viewModel.checkFinishActivityDuringOnPause(
|
|
||||||
isActivityFinishing = false,
|
|
||||||
isChangingConfigurations = false,
|
|
||||||
scope = this
|
|
||||||
)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(activityResults.size).isEqualTo(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCheckFinishActivityDuringOnPause_doNothingIfIsActivityFinishing() = runTest {
|
|
||||||
val activityResults = listOfSetResultFlow()
|
|
||||||
|
|
||||||
viewModel.checkFinishActivityDuringOnPause(
|
|
||||||
isActivityFinishing = true,
|
|
||||||
isChangingConfigurations = false,
|
|
||||||
scope = this
|
|
||||||
)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(activityResults.size).isEqualTo(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCheckFinishActivityDuringOnPause_doNothingIfIsChangingConfigurations() = runTest {
|
|
||||||
val activityResults = listOfSetResultFlow()
|
|
||||||
|
|
||||||
viewModel.checkFinishActivityDuringOnPause(
|
|
||||||
isActivityFinishing = false,
|
|
||||||
isChangingConfigurations = true,
|
|
||||||
scope = this
|
|
||||||
)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(activityResults.size).isEqualTo(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCheckFinishActivityDuringOnPause_defaultFinishSelf() = runTest {
|
|
||||||
val activityResults = listOfSetResultFlow()
|
|
||||||
|
|
||||||
viewModel.checkFinishActivityDuringOnPause(
|
|
||||||
isActivityFinishing = false,
|
|
||||||
isChangingConfigurations = false,
|
|
||||||
scope = backgroundScope
|
|
||||||
)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(activityResults.size).isEqualTo(1)
|
|
||||||
assertThat(activityResults[0].resultCode).isEqualTo(BiometricEnrollBase.RESULT_TIMEOUT)
|
|
||||||
assertThat(activityResults[0].data).isEqualTo(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun TestScope.listOfSetResultFlow(): List<ActivityResult> =
|
|
||||||
mutableListOf<ActivityResult>().also {
|
|
||||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
|
|
||||||
viewModel.setResultFlow.toList(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.android.settings.biometrics2.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.text.TextUtils
|
|
||||||
import com.android.settings.biometrics.BiometricEnrollBase
|
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentRequest
|
|
||||||
import com.google.android.setupcompat.util.WizardManagerHelper
|
|
||||||
|
|
||||||
object EnrollmentRequestUtils {
|
|
||||||
@JvmStatic
|
|
||||||
fun newAllFalseRequest(context: Context): EnrollmentRequest {
|
|
||||||
return newRequest(
|
|
||||||
context = context,
|
|
||||||
isSuw = false,
|
|
||||||
isSuwDeferred = false,
|
|
||||||
isSuwPortal = false,
|
|
||||||
isSuwSuggestedActionFlow = false,
|
|
||||||
isSuwFirstRun = false,
|
|
||||||
isFromSettingsSummery = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun newIsSuwRequest(context: Context): EnrollmentRequest {
|
|
||||||
return newRequest(
|
|
||||||
context = context,
|
|
||||||
isSuw = true,
|
|
||||||
isSuwDeferred = false,
|
|
||||||
isSuwPortal = false,
|
|
||||||
isSuwSuggestedActionFlow = false,
|
|
||||||
isSuwFirstRun = false,
|
|
||||||
isFromSettingsSummery = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun newIsSuwDeferredRequest(context: Context): EnrollmentRequest {
|
|
||||||
return newRequest(
|
|
||||||
context = context,
|
|
||||||
isSuw = true,
|
|
||||||
isSuwDeferred = true,
|
|
||||||
isSuwPortal = false,
|
|
||||||
isSuwSuggestedActionFlow = false,
|
|
||||||
isSuwFirstRun = false,
|
|
||||||
isFromSettingsSummery = false, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun newIsSuwPortalRequest(context: Context): EnrollmentRequest {
|
|
||||||
return newRequest(
|
|
||||||
context = context,
|
|
||||||
isSuw = true,
|
|
||||||
isSuwDeferred = false,
|
|
||||||
isSuwPortal = true,
|
|
||||||
isSuwSuggestedActionFlow = false,
|
|
||||||
isSuwFirstRun = false,
|
|
||||||
isFromSettingsSummery = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun newIsSuwSuggestedActionFlowRequest(
|
|
||||||
context: Context
|
|
||||||
): EnrollmentRequest {
|
|
||||||
return newRequest(
|
|
||||||
context = context,
|
|
||||||
isSuw = true,
|
|
||||||
isSuwDeferred = false,
|
|
||||||
isSuwPortal = false,
|
|
||||||
isSuwSuggestedActionFlow = true,
|
|
||||||
isSuwFirstRun = false,
|
|
||||||
isFromSettingsSummery = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun newRequest(
|
|
||||||
context: Context,
|
|
||||||
isSuw: Boolean,
|
|
||||||
isSuwDeferred: Boolean,
|
|
||||||
isSuwPortal: Boolean,
|
|
||||||
isSuwSuggestedActionFlow: Boolean,
|
|
||||||
isSuwFirstRun: Boolean,
|
|
||||||
isFromSettingsSummery: Boolean,
|
|
||||||
theme: String? = null
|
|
||||||
): EnrollmentRequest {
|
|
||||||
val i = Intent()
|
|
||||||
i.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, isSuw)
|
|
||||||
i.putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, isSuwDeferred)
|
|
||||||
i.putExtra(WizardManagerHelper.EXTRA_IS_PORTAL_SETUP, isSuwPortal)
|
|
||||||
i.putExtra(WizardManagerHelper.EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW, isSuwSuggestedActionFlow)
|
|
||||||
i.putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, isSuwFirstRun)
|
|
||||||
i.putExtra(BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY, isFromSettingsSummery)
|
|
||||||
if (!TextUtils.isEmpty(theme)) {
|
|
||||||
i.putExtra(WizardManagerHelper.EXTRA_THEME, theme)
|
|
||||||
}
|
|
||||||
return EnrollmentRequest(i, context, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.biometrics2.utils;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.doAnswer;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
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 android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public class FingerprintRepositoryUtils {
|
|
||||||
|
|
||||||
public static void setupSuwMaxFingerprintsEnrollable(
|
|
||||||
@NonNull Context context,
|
|
||||||
@NonNull Resources mockedResources,
|
|
||||||
int numOfFp) {
|
|
||||||
final int resId = context.getResources().getIdentifier("suw_max_fingerprints_enrollable",
|
|
||||||
"integer", context.getPackageName());
|
|
||||||
when(mockedResources.getInteger(resId)).thenReturn(numOfFp);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FingerprintRepository newFingerprintRepository(
|
|
||||||
@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 */));
|
|
||||||
doAnswer(invocation -> {
|
|
||||||
final IFingerprintAuthenticatorsRegisteredCallback callback =
|
|
||||||
invocation.getArgument(0);
|
|
||||||
callback.onAllAuthenticatorsRegistered(props);
|
|
||||||
return null;
|
|
||||||
}).when(mockedFingerprintManager).addAuthenticatorsRegisteredCallback(any());
|
|
||||||
return new FingerprintRepository(mockedFingerprintManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,7 +25,6 @@ import com.android.settings.accounts.AccountFeatureProvider;
|
|||||||
import com.android.settings.applications.ApplicationFeatureProvider;
|
import com.android.settings.applications.ApplicationFeatureProvider;
|
||||||
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
import com.android.settings.biometrics.face.FaceFeatureProvider;
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider;
|
import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider;
|
||||||
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
|
|
||||||
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
import com.android.settings.bluetooth.BluetoothFeatureProvider;
|
||||||
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider;
|
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider;
|
||||||
import com.android.settings.connecteddevice.stylus.StylusFeatureProvider;
|
import com.android.settings.connecteddevice.stylus.StylusFeatureProvider;
|
||||||
@@ -82,7 +81,6 @@ public class FakeFeatureFactory extends FeatureFactory {
|
|||||||
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
|
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
|
||||||
public final FaceFeatureProvider mFaceFeatureProvider;
|
public final FaceFeatureProvider mFaceFeatureProvider;
|
||||||
public final FingerprintFeatureProvider mFingerprintFeatureProvider;
|
public final FingerprintFeatureProvider mFingerprintFeatureProvider;
|
||||||
public final BiometricsRepositoryProvider mBiometricsRepositoryProvider;
|
|
||||||
|
|
||||||
public PanelFeatureProvider panelFeatureProvider;
|
public PanelFeatureProvider panelFeatureProvider;
|
||||||
public SlicesFeatureProvider slicesFeatureProvider;
|
public SlicesFeatureProvider slicesFeatureProvider;
|
||||||
@@ -140,7 +138,6 @@ public class FakeFeatureFactory extends FeatureFactory {
|
|||||||
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
|
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
|
||||||
mFaceFeatureProvider = mock(FaceFeatureProvider.class);
|
mFaceFeatureProvider = mock(FaceFeatureProvider.class);
|
||||||
mFingerprintFeatureProvider = mock(FingerprintFeatureProvider.class);
|
mFingerprintFeatureProvider = mock(FingerprintFeatureProvider.class);
|
||||||
mBiometricsRepositoryProvider = mock(BiometricsRepositoryProvider.class);
|
|
||||||
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
|
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
|
||||||
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
|
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
|
||||||
mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
|
mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
|
||||||
@@ -272,11 +269,6 @@ public class FakeFeatureFactory extends FeatureFactory {
|
|||||||
return mFingerprintFeatureProvider;
|
return mFingerprintFeatureProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public BiometricsRepositoryProvider getBiometricsRepositoryProvider() {
|
|
||||||
return mBiometricsRepositoryProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WifiTrackerLibProvider getWifiTrackerLibProvider() {
|
public WifiTrackerLibProvider getWifiTrackerLibProvider() {
|
||||||
return wifiTrackerLibProvider;
|
return wifiTrackerLibProvider;
|
||||||
|
|||||||
Reference in New Issue
Block a user