[BiometricsV2] Refactor EnrollmentViewModel

Refactor FingerprintEnrollmentViewModel to kotlin

Bug: 286198096
Test: atest FingerprintEnrollmentViewModelTest
Test: atest FingerprintEnrollmentActivityTest
Test: atest biometrics-enrollment-test
Test: manually test enrollment
Change-Id: If1b87fa115db1c3fde853ac13fe6204879d34ca8
This commit is contained in:
Milton Wu
2023-07-11 14:54:56 +08:00
parent f94932801a
commit 9a7afc9216
5 changed files with 398 additions and 590 deletions

View File

@@ -130,6 +130,8 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
viewModelProvider[FingerprintEnrollErrorDialogViewModel::class.java]
}
private var isFirstFragmentAdded = false
private val introActionObserver: Observer<Int> = Observer<Int> { action ->
if (DEBUG) {
Log.d(TAG, "introActionObserver($action)")
@@ -168,7 +170,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.onRestoreInstanceState(savedInstanceState)
autoCredentialViewModel.setCredentialModel(savedInstanceState, intent)
// Theme
@@ -181,12 +182,12 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
val fragment: Fragment? = supportFragmentManager.findFragmentById(
R.id.fragment_container_view
)
if (DEBUG) {
Log.d(
TAG, "onCreate() has savedInstance:" + (savedInstanceState != null)
+ ", fragment:" + fragment
)
}
Log.d(
TAG,
"onCreate() has savedInstance:$(savedInstanceState != null), fragment:$fragment"
)
isFirstFragmentAdded = (savedInstanceState != null)
if (fragment == null) {
checkCredential()
if (viewModel.request.isSkipFindSensor) {
@@ -255,12 +256,12 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
}
private fun startFragment(fragmentClass: Class<out Fragment>, tag: String) {
if (!viewModel.isFirstFragmentAdded) {
if (!isFirstFragmentAdded) {
supportFragmentManager.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.fragment_container_view, fragmentClass, null, tag)
.commit()
viewModel.setIsFirstFragmentAdded()
isFirstFragmentAdded = true
} else {
supportFragmentManager.beginTransaction()
.setReorderingAllowed(true)
@@ -300,9 +301,9 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
// 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()) {
val fragmentClass: Class<out Fragment> = if (viewModel.canAssumeUdfps) {
FingerprintEnrollFindUdfpsFragment::class.java
} else if (viewModel.canAssumeSfps()) {
} else if (viewModel.canAssumeSfps) {
FingerprintEnrollFindSfpsFragment::class.java
} else {
FingerprintEnrollFindRfpsFragment::class.java
@@ -327,9 +328,9 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
// 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()) {
val fragmentClass: Class<out Fragment> = if (viewModel.canAssumeUdfps) {
FingerprintEnrollEnrollingUdfpsFragment::class.java
} else if (viewModel.canAssumeSfps()) {
} else if (viewModel.canAssumeSfps) {
FingerprintEnrollEnrollingSfpsFragment::class.java
} else {
FingerprintEnrollEnrollingRfpsFragment::class.java
@@ -345,7 +346,7 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
}
private fun startFinishFragment() {
viewModel.setIsNewFingerprintAdded()
viewModel.isNewFingerprintAdded = true
attachFinishViewModel()
if (viewModel.request.isSkipFindSensor) {
// Set page to Finish
@@ -434,7 +435,7 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
viewModel.request.isSuw,
viewModel.request.suwExtras
)
if (!viewModel.isWaitingActivityResult().compareAndSet(false, true)) {
if (!viewModel.isWaitingActivityResult.compareAndSet(false, true)) {
Log.w(TAG, "chooseLock, fail to set isWaiting flag to true")
}
chooseLockLauncher.launch(intent)
@@ -452,7 +453,7 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
// is already set.
Log.e(TAG, "confirmLock, launched is true")
finish()
} else if (!viewModel.isWaitingActivityResult().compareAndSet(false, true)) {
} else if (!viewModel.isWaitingActivityResult.compareAndSet(false, true)) {
Log.w(TAG, "confirmLock, fail to set isWaiting flag to true")
}
return
@@ -464,7 +465,7 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
}
private fun onChooseOrConfirmLockResult(isChooseLock: Boolean, activityResult: ActivityResult) {
if (!viewModel.isWaitingActivityResult().compareAndSet(true, false)) {
if (!viewModel.isWaitingActivityResult.compareAndSet(true, false)) {
Log.w(TAG, "isChooseLock:$isChooseLock, fail to unset waiting flag")
}
if (autoCredentialViewModel.checkNewCredentialFromActivityResult(
@@ -631,7 +632,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
viewModel.onSaveInstanceState(outState)
autoCredentialViewModel.onSaveInstanceState(outState)
}

View File

@@ -1,234 +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.android.settings.biometrics.fingerprint.FingerprintEnrollFinish.FINGERPRINT_SUGGESTION_ACTIVITY;
import static com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction.EXTRA_FINGERPRINT_ENROLLED_COUNT;
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.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Fingerprint enrollment view model implementation
*/
public class FingerprintEnrollmentViewModel extends AndroidViewModel {
private static final String TAG = "FingerprintEnrollmentViewModel";
@VisibleForTesting
static final String SAVED_STATE_IS_WAITING_ACTIVITY_RESULT = "is_waiting_activity_result";
@VisibleForTesting
static final String SAVED_STATE_IS_NEW_FINGERPRINT_ADDED = "is_new_fingerprint_added";
@VisibleForTesting
static final String SAVED_STATE_IS_FIRST_FRAGMENT_ADDED = "is_first_fragment_added";
@NonNull private final FingerprintRepository mFingerprintRepository;
private final AtomicBoolean mIsWaitingActivityResult = new AtomicBoolean(false);
private final MutableLiveData<ActivityResult> mSetResultLiveData = new MutableLiveData<>();
@NonNull private final EnrollmentRequest mRequest;
private boolean mIsNewFingerprintAdded = false;
/** Flag for FragmentManager::addToBackStack() */
private boolean mIsFirstFragmentAdded = false;
public FingerprintEnrollmentViewModel(
@NonNull Application application,
@NonNull FingerprintRepository fingerprintRepository,
@NonNull EnrollmentRequest request) {
super(application);
mFingerprintRepository = fingerprintRepository;
mRequest = request;
}
/**
* Get EnrollmentRequest
*/
@NonNull
public EnrollmentRequest getRequest() {
return mRequest;
}
/**
* 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(), .
*/
@NonNull
public ActivityResult getOverrideActivityResult(@NonNull ActivityResult result,
@Nullable Bundle generatingChallengeExtras) {
final int newResultCode = mIsNewFingerprintAdded
? BiometricEnrollBase.RESULT_FINISHED
: (mRequest.isAfterSuwOrSuwSuggestedAction()
? BiometricEnrollBase.RESULT_CANCELED
: result.getResultCode());
Intent newData = result.getData();
if (newResultCode == BiometricEnrollBase.RESULT_FINISHED
&& generatingChallengeExtras != null) {
if (newData == null) {
newData = new Intent();
}
newData.putExtras(generatingChallengeExtras);
}
return new 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.
*/
public void checkFinishActivityDuringOnPause(boolean isActivityFinishing,
boolean isChangingConfigurations) {
if (isChangingConfigurations || isActivityFinishing || mRequest.isSuw()
|| isWaitingActivityResult().get()) {
return;
}
mSetResultLiveData.postValue(
new ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null));
}
/**
* Get Suw fingerprint count extra for statistics
*/
@NonNull
public Bundle getSuwFingerprintCountExtra(int userId) {
final Bundle bundle = new Bundle();
bundle.putInt(EXTRA_FINGERPRINT_ENROLLED_COUNT,
mFingerprintRepository.getNumOfEnrolledFingerprintsSize(userId));
return bundle;
}
@NonNull
public LiveData<ActivityResult> getSetResultLiveData() {
return mSetResultLiveData;
}
@NonNull
public AtomicBoolean isWaitingActivityResult() {
return mIsWaitingActivityResult;
}
/**
* Handle savedInstanceState from activity onCreated()
*/
public void onRestoreInstanceState(@Nullable Bundle savedInstanceState) {
if (savedInstanceState == null) {
return;
}
mIsWaitingActivityResult.set(
savedInstanceState.getBoolean(SAVED_STATE_IS_WAITING_ACTIVITY_RESULT, false)
);
mIsNewFingerprintAdded = savedInstanceState.getBoolean(
SAVED_STATE_IS_NEW_FINGERPRINT_ADDED, false);
mIsFirstFragmentAdded = savedInstanceState.getBoolean(
SAVED_STATE_IS_FIRST_FRAGMENT_ADDED, false);
}
/**
* Handle onSaveInstanceState from activity
*/
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBoolean(SAVED_STATE_IS_WAITING_ACTIVITY_RESULT, mIsWaitingActivityResult.get());
outState.putBoolean(SAVED_STATE_IS_NEW_FINGERPRINT_ADDED, mIsNewFingerprintAdded);
outState.putBoolean(SAVED_STATE_IS_FIRST_FRAGMENT_ADDED, mIsFirstFragmentAdded);
}
/**
* Gets the result about fingerprint enrollable
*/
public boolean isMaxEnrolledReached(int userId) {
return mFingerprintRepository.getMaxFingerprints()
<= mFingerprintRepository.getNumOfEnrolledFingerprintsSize(userId);
}
/**
* The first sensor type is UDFPS sensor or not
*/
public boolean canAssumeUdfps() {
return mFingerprintRepository.canAssumeUdfps();
}
/**
* The first sensor type is side fps sensor or not
*/
public boolean canAssumeSfps() {
return mFingerprintRepository.canAssumeSfps();
}
/**
* Sets mIsNewFingerprintAdded to true
*/
public void setIsNewFingerprintAdded() {
mIsNewFingerprintAdded = true;
}
public boolean isFirstFragmentAdded() {
return mIsFirstFragmentAdded;
}
/**
* set mIsFirstFragmentAdded to true, this flag will be used during adding fragment
*/
public void setIsFirstFragmentAdded() {
mIsFirstFragmentAdded = true;
}
/**
* Update FINGERPRINT_SUGGESTION_ACTIVITY into package manager
*/
public void updateFingerprintSuggestionEnableState(int userId) {
final int enrolled = mFingerprintRepository.getNumOfEnrolledFingerprintsSize(userId);
// 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.
final int flag = (enrolled == 1) ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
ComponentName componentName = new ComponentName(getApplication(),
FINGERPRINT_SUGGESTION_ACTIVITY);
getApplication().getPackageManager().setComponentEnabledSetting(componentName, flag,
PackageManager.DONT_KILL_APP);
Log.d(TAG, FINGERPRINT_SUGGESTION_ACTIVITY + " enabled state = " + (enrolled == 1));
}
}

View File

@@ -0,0 +1,158 @@
/*
* 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 androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
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
/**
* 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 _setResultLiveData = MutableLiveData<ActivityResult>()
val setResultLiveData: LiveData<ActivityResult>
get() = _setResultLiveData
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
) {
if (isChangingConfigurations || isActivityFinishing || request.isSuw
|| isWaitingActivityResult.value
) {
return
}
_setResultLiveData.postValue(
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) {
val enrolled = fingerprintRepository.getNumOfEnrolledFingerprintsSize(userId)
// 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.
getApplication<Application>().packageManager.setComponentEnabledSetting(
ComponentName(
getApplication(),
FINGERPRINT_SUGGESTION_ACTIVITY
),
if (enrolled == 1)
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
else
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
Log.d(TAG, "$FINGERPRINT_SUGGESTION_ACTIVITY enabled state = ${enrolled == 1}")
}
companion object {
private const val TAG = "FingerprintEnrollmentViewModel"
}
}