Fix FingerprintEnrollmentActivity rotaiton crash

1. When FragmentActivity rotated, FragmentManager will call default
   fragment constructor w/o parameters. Remove parameters of
   FingerprintEnrollIntroFragment constructor. And also remove
   BiometricsFragmentFactory because of useless now.
2. Remove some LiveData inside AutoCredentialViewModel because it causes
   jitter on activity transition.
3. Save and restore data inside AutoCredentialViewModel for handling
   activity recreation during rotation.
4. clear FingerprintEnrollIntroViewModel.mActionLiveData to prevent
   that activity gets previous action after recreate
5. Fix launching wrong activity during setupwizard

Bug: 259626932
Test: atest AutoCredentialViewModelTest CredentialModelTest
            FingerprintEnrollIntroViewModelTest
Change-Id: Ia26c86dc99ad91dbddef90538d9f5e5583585063
This commit is contained in:
Milton Wu
2022-11-22 16:37:11 +08:00
parent f137463ddc
commit 5174310392
9 changed files with 520 additions and 246 deletions

View File

@@ -1,57 +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.app.admin.DevicePolicyManager;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentFactory;
import androidx.lifecycle.ViewModelProvider;
import com.android.settings.biometrics2.ui.view.FingerprintEnrollIntroFragment;
/**
* Fragment factory for biometrics
*/
public class BiometricsFragmentFactory extends FragmentFactory {
private final Application mApplication;
private final ViewModelProvider mViewModelProvider;
public BiometricsFragmentFactory(Application application,
ViewModelProvider viewModelProvider) {
mApplication = application;
mViewModelProvider = viewModelProvider;
}
@NonNull
@Override
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
final Class<? extends Fragment> clazz = loadFragmentClass(classLoader, className);
if (FingerprintEnrollIntroFragment.class.equals(clazz)) {
final DevicePolicyManager devicePolicyManager =
mApplication.getSystemService(DevicePolicyManager.class);
if (devicePolicyManager != null) {
return new FingerprintEnrollIntroFragment(mViewModelProvider,
devicePolicyManager.getResources());
}
}
return super.instantiate(classLoader, className);
}
}

View File

@@ -22,6 +22,7 @@ import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_C
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
import androidx.annotation.NonNull;
@@ -80,17 +81,30 @@ public final class CredentialModel {
@Nullable
private Long mClearGkPwHandleMillis = null;
public CredentialModel(@NonNull Intent intent, @NonNull Clock clock) {
mUserId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
mSensorId = intent.getIntExtra(EXTRA_KEY_SENSOR_ID, INVALID_SENSOR_ID);
mChallenge = intent.getLongExtra(EXTRA_KEY_CHALLENGE, INVALID_CHALLENGE);
mToken = intent.getByteArrayExtra(EXTRA_KEY_CHALLENGE_TOKEN);
mGkPwHandle = intent.getLongExtra(EXTRA_KEY_GK_PW_HANDLE,
INVALID_GK_PW_HANDLE);
public CredentialModel(@NonNull Bundle bundle, @NonNull Clock clock) {
mUserId = bundle.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId());
mSensorId = bundle.getInt(EXTRA_KEY_SENSOR_ID, INVALID_SENSOR_ID);
mChallenge = bundle.getLong(EXTRA_KEY_CHALLENGE, INVALID_CHALLENGE);
mToken = bundle.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN);
mGkPwHandle = bundle.getLong(EXTRA_KEY_GK_PW_HANDLE, INVALID_GK_PW_HANDLE);
mClock = clock;
mInitMillis = mClock.millis();
}
/**
* Get a bundle which can be used to recreate CredentialModel
*/
@NonNull
public Bundle getBundle() {
final Bundle bundle = new Bundle();
bundle.putInt(Intent.EXTRA_USER_ID, mUserId);
bundle.putInt(EXTRA_KEY_SENSOR_ID, mSensorId);
bundle.putLong(EXTRA_KEY_CHALLENGE, mChallenge);
bundle.putByteArray(EXTRA_KEY_CHALLENGE_TOKEN, mToken);
bundle.putLong(EXTRA_KEY_GK_PW_HANDLE, mGkPwHandle);
return bundle;
}
/**
* Get userId for this credential
*/

View File

@@ -25,7 +25,7 @@ import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroSt
import static com.google.android.setupdesign.util.DynamicColorPalette.ColorType.ACCENT;
import android.app.Activity;
import android.app.admin.DevicePolicyResourcesManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@@ -63,9 +63,6 @@ public class FingerprintEnrollIntroFragment extends Fragment {
private static final String TAG = "FingerprintEnrollIntroFragment";
@NonNull private final ViewModelProvider mViewModelProvider;
@Nullable private final DevicePolicyResourcesManager mDevicePolicyMgrRes;
private FingerprintEnrollIntroViewModel mViewModel = null;
private View mView = null;
@@ -75,12 +72,8 @@ public class FingerprintEnrollIntroFragment extends Fragment {
private TextView mFooterMessage6 = null;
@Nullable private PorterDuffColorFilter mIconColorFilter;
public FingerprintEnrollIntroFragment(
@NonNull ViewModelProvider viewModelProvider,
@Nullable DevicePolicyResourcesManager devicePolicyMgrRes) {
public FingerprintEnrollIntroFragment() {
super();
mViewModelProvider = viewModelProvider;
mDevicePolicyMgrRes = devicePolicyMgrRes;
}
@Nullable
@@ -197,7 +190,8 @@ public class FingerprintEnrollIntroFragment extends Fragment {
@Override
public void onAttach(@NonNull Context context) {
mViewModel = mViewModelProvider.get(FingerprintEnrollIntroViewModel.class);
mViewModel = new ViewModelProvider(getActivity())
.get(FingerprintEnrollIntroViewModel.class);
getLifecycle().addObserver(mViewModel);
super.onAttach(context);
}
@@ -232,12 +226,16 @@ public class FingerprintEnrollIntroFragment extends Fragment {
private String getDescriptionDisabledByAdmin(@NonNull Context context) {
final int defaultStrId =
R.string.security_settings_fingerprint_enroll_introduction_message_unlock_disabled;
if (mDevicePolicyMgrRes == null) {
final DevicePolicyManager devicePolicyManager = getActivity()
.getSystemService(DevicePolicyManager.class);
if (devicePolicyManager != null) {
return devicePolicyManager.getResources().getString(FINGERPRINT_UNLOCK_DISABLED,
() -> context.getString(defaultStrId));
} else {
Log.w(TAG, "getDescriptionDisabledByAdmin, null device policy manager res");
return "";
}
return mDevicePolicyMgrRes.getString(FINGERPRINT_UNLOCK_DISABLED,
() -> context.getString(defaultStrId));
}
private void setHeaderText(@NonNull Activity activity, int resId) {

View File

@@ -19,20 +19,19 @@ package com.android.settings.biometrics2.ui.view;
import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY;
import static com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR;
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE;
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK;
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK;
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_IS_GENERATING_CHALLENGE;
import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_VALID;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL;
import android.app.Activity;
import android.app.Application;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import androidx.activity.result.ActivityResult;
@@ -42,7 +41,6 @@ import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.viewmodel.CreationExtras;
import androidx.lifecycle.viewmodel.MutableCreationExtras;
@@ -51,11 +49,9 @@ import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor;
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollEnrolling;
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor;
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
import com.android.settings.biometrics2.factory.BiometricsFragmentFactory;
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory;
import com.android.settings.biometrics2.ui.model.CredentialModel;
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel;
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.FingerprintChallengeGenerator;
@@ -102,12 +98,12 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
getLifecycle().addObserver(mViewModel);
mAutoCredentialViewModel = viewModelProvider.get(AutoCredentialViewModel.class);
mAutoCredentialViewModel.setCredentialModel(new CredentialModel(getIntent(),
SystemClock.elapsedRealtimeClock()));
getLifecycle().addObserver(mAutoCredentialViewModel);
mAutoCredentialViewModel.setCredentialModel(savedInstanceState, getIntent());
mAutoCredentialViewModel.getGenerateChallengeFailLiveData().observe(this,
this::onGenerateChallengeFail);
mViewModel.getSetResultLiveData().observe(this, this::onSetActivityResult);
mAutoCredentialViewModel.getActionLiveData().observe(this, this::onCredentialAction);
checkCredential();
// Theme
setTheme(mViewModel.getRequest().getTheme());
@@ -116,21 +112,29 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
// fragment
setContentView(R.layout.biometric_enrollment_container);
final FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.setFragmentFactory(
new BiometricsFragmentFactory(getApplication(), viewModelProvider));
final FingerprintEnrollIntroViewModel fingerprintEnrollIntroViewModel =
viewModelProvider.get(FingerprintEnrollIntroViewModel.class);
fingerprintEnrollIntroViewModel.setEnrollmentRequest(mViewModel.getRequest());
fingerprintEnrollIntroViewModel.setUserId(mAutoCredentialViewModel.getUserId());
// Clear ActionLiveData in FragmentViewModel to prevent getting previous action when
// recreate
fingerprintEnrollIntroViewModel.clearActionLiveData();
fingerprintEnrollIntroViewModel.getActionLiveData().observe(
this, this::observeIntroAction);
final String tag = "FingerprintEnrollIntroFragment";
fragmentManager.beginTransaction()
.setReorderingAllowed(true)
.add(R.id.fragment_container_view, FingerprintEnrollIntroFragment.class, null, tag)
.commit();
if (savedInstanceState == null) {
final String tag = "FingerprintEnrollIntroFragment";
getSupportFragmentManager().beginTransaction()
.setReorderingAllowed(true)
.add(R.id.fragment_container_view, FingerprintEnrollIntroFragment.class, null,
tag)
.commit();
}
}
private void onGenerateChallengeFail(@NonNull Boolean isFail) {
onSetActivityResult(new ActivityResult(RESULT_CANCELED, null));
}
private void onSetActivityResult(@NonNull ActivityResult result) {
@@ -141,8 +145,8 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
finish();
}
private void onCredentialAction(@NonNull Integer action) {
switch (action) {
private void checkCredential() {
switch (mAutoCredentialViewModel.checkCredential()) {
case CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK: {
final Intent intent = mAutoCredentialViewModel.getChooseLockIntent(this,
mViewModel.getRequest().isSuw(), mViewModel.getRequest().getSuwExtras());
@@ -168,12 +172,9 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
}
return;
}
case CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE: {
Log.w(TAG, "observeCredentialLiveData, finish with action:" + action);
if (mViewModel.getRequest().isAfterSuwOrSuwSuggestedAction()) {
setResult(Activity.RESULT_CANCELED);
}
finish();
case CREDENTIAL_VALID:
case CREDENTIAL_IS_GENERATING_CHALLENGE: {
// Do nothing
}
}
}
@@ -186,10 +187,15 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
if (mAutoCredentialViewModel.checkNewCredentialFromActivityResult(
isChooseLock, activityResult)) {
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
} else {
onSetActivityResult(activityResult);
}
}
private void observeIntroAction(@NonNull Integer action) {
private void observeIntroAction(@Nullable Integer action) {
if (action == null) {
return;
}
switch (action) {
case FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH: {
onSetActivityResult(
@@ -207,9 +213,9 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
Log.w(TAG, "startNext, isSuw:" + isSuw + ", fail to set isWaiting flag");
}
final Intent intent = new Intent(this, isSuw
? SetupFingerprintEnrollEnrolling.class
? SetupFingerprintEnrollFindSensor.class
: FingerprintEnrollFindSensor.class);
intent.putExtras(mAutoCredentialViewModel.getCredentialBundle());
intent.putExtras(mAutoCredentialViewModel.getCredentialIntentExtra());
intent.putExtras(mViewModel.getNextActivityBaseIntentExtras());
mNextActivityLauncher.launch(intent);
}
@@ -272,5 +278,6 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
mViewModel.onSaveInstanceState(outState);
mAutoCredentialViewModel.onSaveInstanceState(outState);
}
}

View File

@@ -30,20 +30,21 @@ import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import androidx.activity.result.ActivityResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.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;
@@ -57,31 +58,40 @@ import java.lang.annotation.RetentionPolicy;
* AutoCredentialViewModel which uses CredentialModel to determine next actions for activity, like
* start ChooseLockActivity, start ConfirmLockActivity, GenerateCredential, or do nothing.
*/
public class AutoCredentialViewModel extends AndroidViewModel implements DefaultLifecycleObserver {
public class AutoCredentialViewModel extends AndroidViewModel {
private static final String TAG = "AutoCredentialViewModel";
private static final boolean DEBUG = true;
@VisibleForTesting
static final String KEY_CREDENTIAL_MODEL = "credential_model";
private static final boolean DEBUG = false;
/**
* Valid credential, activity doesn't need to do anything.
*/
public static final int CREDENTIAL_VALID = 0;
/**
* This credential looks good, but still need to run generateChallenge().
*/
public static final int CREDENTIAL_IS_GENERATING_CHALLENGE = 1;
/**
* Need activity to run choose lock
*/
public static final int CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK = 1;
public static final int CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK = 2;
/**
* Need activity to run confirm lock
*/
public static final int CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK = 2;
/**
* Fail to use challenge from hardware generateChallenge(), shall finish activity with proper
* error code
*/
public static final int CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE = 3;
public static final int CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK = 3;
@IntDef(prefix = { "CREDENTIAL_" }, value = {
CREDENTIAL_VALID,
CREDENTIAL_IS_GENERATING_CHALLENGE,
CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK,
CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK,
CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE
CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK
})
@Retention(RetentionPolicy.SOURCE)
public @interface CredentialAction {}
@@ -157,11 +167,10 @@ public class AutoCredentialViewModel extends AndroidViewModel implements Default
}
}
@NonNull private final LockPatternUtils mLockPatternUtils;
@NonNull private final ChallengeGenerator mChallengeGenerator;
private CredentialModel mCredentialModel = null;
@NonNull private final MutableLiveData<Integer> mActionLiveData =
@NonNull private final MutableLiveData<Boolean> mGenerateChallengeFailLiveData =
new MutableLiveData<>();
public AutoCredentialViewModel(
@@ -173,51 +182,63 @@ public class AutoCredentialViewModel extends AndroidViewModel implements Default
mChallengeGenerator = challengeGenerator;
}
public void setCredentialModel(@NonNull CredentialModel credentialModel) {
mCredentialModel = credentialModel;
/**
* Set CredentialModel, the source is coming from savedInstanceState or activity intent
*/
public void setCredentialModel(@Nullable Bundle savedInstanceState, @NonNull Intent intent) {
mCredentialModel = new CredentialModel(
savedInstanceState != null
? savedInstanceState.getBundle(KEY_CREDENTIAL_MODEL)
: intent.getExtras(),
SystemClock.elapsedRealtimeClock());
if (DEBUG) {
Log.d(TAG, "setCredentialModel " + mCredentialModel + ", savedInstanceState exist:"
+ (savedInstanceState != null));
}
}
/**
* Observe ActionLiveData for actions about choosing lock, confirming lock, or finishing
* activity
* Handle onSaveInstanceState from activity
*/
@NonNull
public LiveData<Integer> getActionLiveData() {
return mActionLiveData;
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBundle(KEY_CREDENTIAL_MODEL, mCredentialModel.getBundle());
}
@Override
public void onCreate(@NonNull LifecycleOwner owner) {
checkCredential();
@NonNull
public LiveData<Boolean> getGenerateChallengeFailLiveData() {
return mGenerateChallengeFailLiveData;
}
/**
* Check credential status for biometric enrollment.
*/
private void checkCredential() {
@CredentialAction
public int checkCredential() {
if (isValidCredential()) {
return;
return CREDENTIAL_VALID;
}
final long gkPwHandle = mCredentialModel.getGkPwHandle();
if (isUnspecifiedPassword()) {
mActionLiveData.postValue(CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK);
return CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK;
} else if (CredentialModel.isValidGkPwHandle(gkPwHandle)) {
generateChallenge(gkPwHandle);
return CREDENTIAL_IS_GENERATING_CHALLENGE;
} else {
mActionLiveData.postValue(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
return CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK;
}
}
private void generateChallenge(long gkPwHandle) {
mChallengeGenerator.setCallback((sensorId, userId, challenge) -> {
mCredentialModel.setSensorId(sensorId);
mCredentialModel.setChallenge(challenge);
try {
final byte[] newToken = requestGatekeeperHat(gkPwHandle, challenge, userId);
mCredentialModel.setSensorId(sensorId);
mCredentialModel.setChallenge(challenge);
mCredentialModel.setToken(newToken);
} catch (IllegalStateException e) {
Log.e(TAG, "generateChallenge, IllegalStateException", e);
mActionLiveData.postValue(CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE);
mGenerateChallengeFailLiveData.postValue(true);
return;
}
@@ -231,7 +252,7 @@ public class AutoCredentialViewModel extends AndroidViewModel implements Default
// Check credential again
if (!isValidCredential()) {
Log.w(TAG, "generateChallenge, invalid Credential");
mActionLiveData.postValue(CREDENTIAL_FAIL_DURING_GENERATE_CHALLENGE);
mGenerateChallengeFailLiveData.postValue(true);
}
});
mChallengeGenerator.generateChallenge(getUserId());
@@ -282,16 +303,16 @@ public class AutoCredentialViewModel extends AndroidViewModel implements Default
final VerifyCredentialResponse response = mLockPatternUtils
.verifyGatekeeperPasswordHandle(gkPwHandle, challenge, userId);
if (!response.isMatched()) {
throw new IllegalStateException("Unable to request Gatekeeper HAT");
throw new GatekeeperCredentialNotMatchException("Unable to request Gatekeeper HAT");
}
return response.getGatekeeperHAT();
}
/**
* Get Credential bundle which will be used to launch next activity.
* Get Credential intent extra which will be used to launch next activity.
*/
@NonNull
public Bundle getCredentialBundle() {
public Bundle getCredentialIntentExtra() {
final Bundle retBundle = new Bundle();
final long gkPwHandle = mCredentialModel.getGkPwHandle();
if (CredentialModel.isValidGkPwHandle(gkPwHandle)) {

View File

@@ -144,6 +144,13 @@ public class FingerprintEnrollIntroViewModel extends AndroidViewModel
return mPageStatusLiveData;
}
/**
* Clear user's action live data (like clicking Agree, Skip, or Done)
*/
public void clearActionLiveData() {
mActionLiveData.setValue(null);
}
/**
* Get user's action live data (like clicking Agree, Skip, or Done)
*/