diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java deleted file mode 100644 index 76c9e953783..00000000000 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java +++ /dev/null @@ -1,637 +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 static androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; -import static androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE; -import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY; - -import static com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR_KEY; -import static com.android.settings.biometrics2.factory.BiometricsViewModelFactory.ENROLLMENT_REQUEST_KEY; -import static com.android.settings.biometrics2.factory.BiometricsViewModelFactory.USER_ID_KEY; -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.FingerprintEnrollEnrollingViewModel.ErrorDialogData; -import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE; -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.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH; -import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT; -import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FingerprintEnrollEnrollingAction; -import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FingerprintErrorDialogAction; -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.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FingerprintEnrollFindSensorAction; -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.ui.viewmodel.FingerprintEnrollFinishViewModel.FingerprintEnrollFinishAction; -import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL; -import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH; -import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL; -import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FingerprintEnrollIntroAction; - -import android.annotation.StyleRes; -import android.app.Application; -import android.content.Intent; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.content.res.Resources; -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.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; -import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModelProvider; -import androidx.lifecycle.viewmodel.CreationExtras; -import androidx.lifecycle.viewmodel.MutableCreationExtras; - -import com.android.settings.R; -import com.android.settings.Utils; -import com.android.settings.biometrics.BiometricEnrollBase; -import com.android.settings.biometrics2.data.repository.FingerprintRepository; -import com.android.settings.biometrics2.factory.BiometricsViewModelFactory; -import com.android.settings.biometrics2.ui.model.CredentialModel; -import com.android.settings.biometrics2.ui.model.EnrollmentRequest; -import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel; -import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.FingerprintChallengeGenerator; -import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel; -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel; -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.google.android.setupdesign.util.ThemeHelper; - -/** - * Fingerprint enrollment activity implementation - */ -public class FingerprintEnrollmentActivity extends FragmentActivity { - - /** - * Setupwizard activity - */ - public static class SetupActivity extends FingerprintEnrollmentActivity {} - - /** - * Internal activity for FingerprintSettings - */ - public static class InternalActivity extends FingerprintEnrollmentActivity {} - - private static final boolean DEBUG = false; - private static final String TAG = "FingerprintEnrollmentActivity"; - - private static final String INTRO_TAG = "intro"; - private static final String FIND_SENSOR_TAG = "find-sensor"; - private static final String ENROLLING_TAG = "enrolling"; - private static final String FINISH_TAG = "finish"; - private static final String SKIP_SETUP_FIND_FPS_DIALOG_TAG = "skip-setup-dialog"; - private static final String ENROLLING_ERROR_DIALOG_TAG = "enrolling-error-dialog"; - - protected static final int LAUNCH_CONFIRM_LOCK_ACTIVITY = 1; - - // This flag is used for addBackStack(), we do not save it in ViewModel because it is just used - // during FragmentManager calls - private boolean mIsFirstFragmentAdded = false; - - private ViewModelProvider mViewModelProvider; - private FingerprintEnrollmentViewModel mViewModel; - private AutoCredentialViewModel mAutoCredentialViewModel; - private final Observer mIntroActionObserver = action -> { - if (DEBUG) { - Log.d(TAG, "mIntroActionObserver(" + action + ")"); - } - if (action != null) { - onIntroAction(action); - } - }; - private final Observer mFindSensorActionObserver = action -> { - if (DEBUG) { - Log.d(TAG, "mFindSensorActionObserver(" + action + ")"); - } - if (action != null) { - onFindSensorAction(action); - } - }; - private final Observer mEnrollingActionObserver = action -> { - if (DEBUG) { - Log.d(TAG, "mEnrollingActionObserver(" + action + ")"); - } - if (action != null) { - onEnrollingAction(action); - } - }; - private final Observer mEnrollingErrorDialogObserver = data -> { - if (DEBUG) { - Log.d(TAG, "mEnrollingErrorDialogObserver(" + data + ")"); - } - if (data != null) { - new FingerprintEnrollEnrollingErrorDialog().show(getSupportFragmentManager(), - ENROLLING_ERROR_DIALOG_TAG); - } - }; - private final Observer mEnrollingErrorDialogActionObserver = action -> { - if (DEBUG) { - Log.d(TAG, "mEnrollingErrorDialogActionObserver(" + action + ")"); - } - if (action != null) { - onEnrollingErrorDialogAction(action); - } - }; - private final Observer mFinishActionObserver = action -> { - if (DEBUG) { - Log.d(TAG, "mFinishActionObserver(" + action + ")"); - } - if (action != null) { - onFinishAction(action); - } - }; - private final ActivityResultCallback mChooseLockResultCallback = - result -> onChooseOrConfirmLockResult(true /* isChooseLock */, result); - private final ActivityResultLauncher mChooseLockLauncher = - registerForActivityResult(new StartActivityForResult(), mChooseLockResultCallback); - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mViewModelProvider = new ViewModelProvider(this); - - mViewModel = mViewModelProvider.get(FingerprintEnrollmentViewModel.class); - mViewModel.setSavedInstanceState(savedInstanceState); - - mAutoCredentialViewModel = mViewModelProvider.get(AutoCredentialViewModel.class); - mAutoCredentialViewModel.setCredentialModel(savedInstanceState, getIntent()); - - // Theme - setTheme(mViewModel.getRequest().getTheme()); - ThemeHelper.trySetDynamicColor(this); - getWindow().setStatusBarColor(android.graphics.Color.TRANSPARENT); - - // fragment - setContentView(R.layout.biometric_enrollment_container); - - final Fragment fragment = getSupportFragmentManager().findFragmentById( - R.id.fragment_container_view); - if (DEBUG) { - Log.d(TAG, "onCreate() has savedInstance:" + (savedInstanceState != null) - + ", fragment:" + fragment); - } - if (fragment == null) { - checkCredential(); - final EnrollmentRequest request = mViewModel.getRequest(); - if (request.isSkipFindSensor()) { - startEnrollingFragment(); - } else if (request.isSkipIntro()) { - startFindSensorFragment(); - } else { - startIntroFragment(); - } - } else { - final String tag = fragment.getTag(); - if (INTRO_TAG.equals(tag)) { - attachIntroViewModel(); - } else if (FIND_SENSOR_TAG.equals(tag)) { - attachFindSensorViewModel(); - attachIntroViewModel(); - } else if (ENROLLING_TAG.equals(tag)) { - attachEnrollingViewModel(); - attachFindSensorViewModel(); - attachIntroViewModel(); - } else if (FINISH_TAG.equals(tag)) { - attachFinishViewModel(); - attachFindSensorViewModel(); - attachIntroViewModel(); - } else { - Log.e(TAG, "fragment tag " + tag + " not found"); - finish(); - return; - } - } - - // observe LiveData - mViewModel.getSetResultLiveData().observe(this, this::onSetActivityResult); - - mAutoCredentialViewModel.getGenerateChallengeFailedLiveData().observe(this, - this::onGenerateChallengeFailed); - } - - private void startFragment(@NonNull Class fragmentClass, - @NonNull String tag) { - if (!mIsFirstFragmentAdded) { - getSupportFragmentManager().beginTransaction() - .setReorderingAllowed(true) - .replace(R.id.fragment_container_view, fragmentClass, null, tag) - .commit(); - mIsFirstFragmentAdded = true; - } else { - getSupportFragmentManager().beginTransaction() - .setReorderingAllowed(true) - .setCustomAnimations(R.anim.shared_x_axis_activity_open_enter_dynamic_color, - R.anim.shared_x_axis_activity_open_exit, - R.anim.shared_x_axis_activity_close_enter_dynamic_color, - R.anim.shared_x_axis_activity_close_exit) - .replace(R.id.fragment_container_view, fragmentClass, null, tag) - .addToBackStack(tag) - .commit(); - } - } - - private void startIntroFragment() { - attachIntroViewModel(); - startFragment(FingerprintEnrollIntroFragment.class, INTRO_TAG); - } - - private void attachIntroViewModel() { - final EnrollmentRequest request = mViewModel.getRequest(); - if (request.isSkipIntro() || request.isSkipFindSensor()) { - return; - } - - final FingerprintEnrollIntroViewModel introViewModel = - mViewModelProvider.get(FingerprintEnrollIntroViewModel.class); - - // Clear ActionLiveData in FragmentViewModel to prevent getting previous action during - // recreate, like press 'Agree' then press 'back' in FingerprintEnrollFindSensor activity. - introViewModel.clearActionLiveData(); - introViewModel.getActionLiveData().observe(this, mIntroActionObserver); - } - - // We need to make sure token is valid before entering find sensor page - private void startFindSensorFragment() { - // Always setToken into progressViewModel even it is not necessary action for UDFPS - mViewModelProvider.get(FingerprintEnrollProgressViewModel.class) - .setToken(mAutoCredentialViewModel.getToken()); - - attachFindSensorViewModel(); - - final Class fragmentClass; - if (mViewModel.canAssumeUdfps()) { - fragmentClass = FingerprintEnrollFindUdfpsFragment.class; - } else if (mViewModel.canAssumeSfps()) { - fragmentClass = FingerprintEnrollFindSfpsFragment.class; - } else { - fragmentClass = FingerprintEnrollFindRfpsFragment.class; - } - startFragment(fragmentClass, FIND_SENSOR_TAG); - } - - private void attachFindSensorViewModel() { - if (mViewModel.getRequest().isSkipFindSensor()) { - return; - } - - final FingerprintEnrollFindSensorViewModel findSensorViewModel = - mViewModelProvider.get(FingerprintEnrollFindSensorViewModel.class); - - // Clear ActionLiveData in FragmentViewModel to prevent getting previous action during - // recreate, like press 'Start' then press 'back' in FingerprintEnrollEnrolling activity. - findSensorViewModel.clearActionLiveData(); - findSensorViewModel.getActionLiveData().observe(this, mFindSensorActionObserver); - } - - private void startEnrollingFragment() { - // Always setToken into progressViewModel even it is not necessary action for SFPS or RFPS - mViewModelProvider.get(FingerprintEnrollProgressViewModel.class) - .setToken(mAutoCredentialViewModel.getToken()); - - attachEnrollingViewModel(); - - final Class fragmentClass; - if (mViewModel.canAssumeUdfps()) { - fragmentClass = FingerprintEnrollEnrollingUdfpsFragment.class; - } else if (mViewModel.canAssumeSfps()) { - fragmentClass = FingerprintEnrollEnrollingSfpsFragment.class; - } else { - fragmentClass = FingerprintEnrollEnrollingRfpsFragment.class; - } - startFragment(fragmentClass, ENROLLING_TAG); - } - - private void attachEnrollingViewModel() { - final FingerprintEnrollEnrollingViewModel enrollingViewModel = - mViewModelProvider.get(FingerprintEnrollEnrollingViewModel.class); - enrollingViewModel.clearActionLiveData(); - enrollingViewModel.getActionLiveData().observe(this, mEnrollingActionObserver); - enrollingViewModel.getErrorDialogLiveData().observe(this, mEnrollingErrorDialogObserver); - enrollingViewModel.getErrorDialogActionLiveData().observe(this, - mEnrollingErrorDialogActionObserver); - } - - private void startFinishFragment() { - mViewModel.setIsNewFingerprintAdded(); - attachFinishViewModel(); - - if (mViewModel.getRequest().isSkipFindSensor()) { - // Set page to Finish - getSupportFragmentManager().beginTransaction() - .setReorderingAllowed(true) - .setCustomAnimations(R.anim.shared_x_axis_activity_open_enter_dynamic_color, - R.anim.shared_x_axis_activity_open_exit, - R.anim.shared_x_axis_activity_close_enter_dynamic_color, - R.anim.shared_x_axis_activity_close_exit) - .replace(R.id.fragment_container_view, FingerprintEnrollFinishFragment.class, - null, FINISH_TAG) - .commit(); - } else { - // Remove Enrolling page - getSupportFragmentManager().popBackStack(); - - // Remove old Finish page if any - if (getSupportFragmentManager().findFragmentByTag(FINISH_TAG) != null) { - getSupportFragmentManager().popBackStack(FINISH_TAG, POP_BACK_STACK_INCLUSIVE); - } - - // Remove FindSensor page if maxEnrolled - if (mViewModel.isMaxEnrolledReached(mAutoCredentialViewModel.getUserId()) - && getSupportFragmentManager().findFragmentByTag(FIND_SENSOR_TAG) != null) { - getSupportFragmentManager().popBackStack(FIND_SENSOR_TAG, POP_BACK_STACK_INCLUSIVE); - } - - // Add Finish page - getSupportFragmentManager().beginTransaction() - .setReorderingAllowed(true) - .setCustomAnimations(R.anim.shared_x_axis_activity_open_enter_dynamic_color, - R.anim.shared_x_axis_activity_open_exit, - R.anim.shared_x_axis_activity_close_enter_dynamic_color, - R.anim.shared_x_axis_activity_close_exit) - .replace(R.id.fragment_container_view, FingerprintEnrollFinishFragment.class, - null, FINISH_TAG) - .addToBackStack(FINISH_TAG) - .commit(); - } - } - - private void attachFinishViewModel() { - final FingerprintEnrollFinishViewModel viewModel = - mViewModelProvider.get(FingerprintEnrollFinishViewModel.class); - viewModel.clearActionLiveData(); - viewModel.getActionLiveData().observe(this, mFinishActionObserver); - } - - private void onGenerateChallengeFailed(@NonNull Boolean ignoredBoolean) { - onSetActivityResult(new ActivityResult(RESULT_CANCELED, null)); - } - - private void onSetActivityResult(@NonNull ActivityResult result) { - final Bundle challengeExtras = mAutoCredentialViewModel.createGeneratingChallengeExtras(); - final ActivityResult overrideResult = mViewModel.getOverrideActivityResult( - result, challengeExtras); - if (DEBUG) { - Log.d(TAG, "onSetActivityResult(" + result + "), override:" + overrideResult - + ") challengeExtras:" + challengeExtras); - } - setResult(overrideResult.getResultCode(), overrideResult.getData()); - finish(); - } - - private void checkCredential() { - switch (mAutoCredentialViewModel.checkCredential()) { - case CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK: { - final Intent intent = mAutoCredentialViewModel.createChooseLockIntent(this, - mViewModel.getRequest().isSuw(), mViewModel.getRequest().getSuwExtras()); - if (!mViewModel.isWaitingActivityResult().compareAndSet(false, true)) { - Log.w(TAG, "chooseLock, fail to set isWaiting flag to true"); - } - mChooseLockLauncher.launch(intent); - return; - } - case CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK: { - final boolean launched = mAutoCredentialViewModel.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 (!mViewModel.isWaitingActivityResult().compareAndSet(false, true)) { - Log.w(TAG, "confirmLock, fail to set isWaiting flag to true"); - } - return; - } - case CREDENTIAL_VALID: - case CREDENTIAL_IS_GENERATING_CHALLENGE: { - // Do nothing - } - } - } - - private void onChooseOrConfirmLockResult(boolean isChooseLock, - @NonNull ActivityResult activityResult) { - if (!mViewModel.isWaitingActivityResult().compareAndSet(true, false)) { - Log.w(TAG, "isChooseLock:" + isChooseLock + ", fail to unset waiting flag"); - } - if (mAutoCredentialViewModel.checkNewCredentialFromActivityResult( - isChooseLock, activityResult)) { - overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); - } else { - onSetActivityResult(activityResult); - } - } - - private void onIntroAction(@FingerprintEnrollIntroAction int action) { - switch (action) { - case FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH: { - onSetActivityResult( - new ActivityResult(BiometricEnrollBase.RESULT_FINISHED, null)); - return; - } - case FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL: { - onSetActivityResult( - new ActivityResult(BiometricEnrollBase.RESULT_SKIP, null)); - return; - } - case FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL: { - startFindSensorFragment(); - } - } - } - - private void onFindSensorAction(@FingerprintEnrollFindSensorAction int action) { - switch (action) { - case FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP: { - onSetActivityResult(new ActivityResult(BiometricEnrollBase.RESULT_SKIP, null)); - return; - } - case FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG: { - new SkipSetupFindFpsDialog().show(getSupportFragmentManager(), - SKIP_SETUP_FIND_FPS_DIALOG_TAG); - return; - } - case FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START: { - startEnrollingFragment(); - } - } - } - - private void onEnrollingAction(@FingerprintEnrollEnrollingAction int action) { - switch (action) { - case FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE: { - startFinishFragment(); - break; - } - case FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_USER_SKIP: { - onSetActivityResult(new ActivityResult(BiometricEnrollBase.RESULT_SKIP, null)); - break; - } - case FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_ICON_TOUCH_DIALOG: { - new FingerprintEnrollEnrollingIconTouchDialog().show(getSupportFragmentManager(), - SKIP_SETUP_FIND_FPS_DIALOG_TAG); - break; - } - case FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_BACK_PRESSED: { - if (getSupportFragmentManager().getBackStackEntryCount() > 0) { - getSupportFragmentManager().popBackStack(); - } else { - onSetActivityResult(new ActivityResult(RESULT_CANCELED, null)); - } - break; - } - } - } - - private void onEnrollingErrorDialogAction(@FingerprintErrorDialogAction int action) { - switch (action) { - case FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH: - onSetActivityResult(new ActivityResult(BiometricEnrollBase.RESULT_FINISHED, null)); - break; - case FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT: - onSetActivityResult(new ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null)); - break; - } - } - - private void onFinishAction(@FingerprintEnrollFinishAction int action) { - switch (action) { - case FINGERPRINT_ENROLL_FINISH_ACTION_ADD_BUTTON_CLICK: { - startEnrollingFragment(); - break; - } - case FINGERPRINT_ENROLL_FINISH_ACTION_NEXT_BUTTON_CLICK: { - final Intent data; - if (mViewModel.getRequest().isSuw()) { - data = new Intent(); - data.putExtras(mViewModel.getSuwFingerprintCountExtra( - mAutoCredentialViewModel.getUserId())); - } else { - data = null; - } - onSetActivityResult(new ActivityResult(BiometricEnrollBase.RESULT_FINISHED, data)); - break; - } - } - } - - @Override - protected void onPause() { - super.onPause(); - mViewModel.checkFinishActivityDuringOnPause(isFinishing(), isChangingConfigurations()); - } - - @Override - protected void onDestroy() { - mViewModel.updateFingerprintSuggestionEnableState(mAutoCredentialViewModel.getUserId()); - super.onDestroy(); - } - - @Override - protected void onApplyThemeResource(Resources.Theme theme, @StyleRes int resid, boolean first) { - theme.applyStyle(R.style.SetupWizardPartnerResource, true); - super.onApplyThemeResource(theme, resid, first); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - if (requestCode == LAUNCH_CONFIRM_LOCK_ACTIVITY) { - onChooseOrConfirmLockResult(false, new ActivityResult(resultCode, data)); - return; - } - super.onActivityResult(requestCode, resultCode, data); - } - - @NonNull - @Override - public CreationExtras getDefaultViewModelCreationExtras() { - final Application application = - super.getDefaultViewModelCreationExtras().get(APPLICATION_KEY); - final MutableCreationExtras ret = new MutableCreationExtras(); - ret.set(APPLICATION_KEY, application); - - final FingerprintRepository repository = FeatureFactory.getFactory(application) - .getBiometricsRepositoryProvider().getFingerprintRepository(application); - ret.set(CHALLENGE_GENERATOR_KEY, new FingerprintChallengeGenerator(repository)); - - ret.set(ENROLLMENT_REQUEST_KEY, new EnrollmentRequest(getIntent(), getApplicationContext(), - this instanceof SetupActivity)); - - Bundle extras = getIntent().getExtras(); - final CredentialModel credentialModel = new CredentialModel(extras, - SystemClock.elapsedRealtimeClock()); - ret.set(USER_ID_KEY, credentialModel.getUserId()); - - return ret; - } - - @NonNull - @Override - public ViewModelProvider.Factory getDefaultViewModelProviderFactory() { - return new BiometricsViewModelFactory(); - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - getWindow().setStatusBarColor(getBackgroundColor()); - } - - @ColorInt - private int getBackgroundColor() { - final ColorStateList stateList = Utils.getColorAttr(this, android.R.attr.windowBackground); - return stateList != null ? stateList.getDefaultColor() : Color.TRANSPARENT; - } - - @Override - public void onConfigurationChanged(@NonNull Configuration newConfig) { - mViewModelProvider.get(DeviceFoldedViewModel.class).onConfigurationChanged(newConfig); - super.onConfigurationChanged(newConfig); - } - - @Override - protected void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - mViewModel.onSaveInstanceState(outState); - mAutoCredentialViewModel.onSaveInstanceState(outState); - } -} diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt new file mode 100644 index 00000000000..e3a60781de7 --- /dev/null +++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt @@ -0,0 +1,644 @@ +/* + * 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.Observer +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras +import androidx.lifecycle.viewmodel.MutableCreationExtras +import com.android.settings.R +import com.android.settings.Utils +import com.android.settings.biometrics.BiometricEnrollBase +import com.android.settings.biometrics2.data.repository.FingerprintRepository +import com.android.settings.biometrics2.factory.BiometricsViewModelFactory +import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR_KEY +import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.ENROLLMENT_REQUEST_KEY +import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.USER_ID_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.CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK +import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK +import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_IS_GENERATING_CHALLENGE +import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_VALID +import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.FingerprintChallengeGenerator +import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.ErrorDialogData +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.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FingerprintEnrollEnrollingAction +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FingerprintErrorDialogAction +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.FingerprintEnrollIntroViewModel +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FingerprintEnrollIntroAction +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel +import com.android.settings.overlay.FeatureFactory +import com.google.android.setupdesign.util.ThemeHelper + +/** + * Fingerprint enrollment activity implementation + */ +open class FingerprintEnrollmentActivity : FragmentActivity() { + /** SetupWizard activity*/ + class SetupActivity : FingerprintEnrollmentActivity() + + /** Internal activity for FingerprintSettings */ + class InternalActivity : FingerprintEnrollmentActivity() + + /** + * This flag is used for addBackStack(), we do not save it in ViewModel because it is just used + * during FragmentManager calls + */ + private var isFirstFragmentAdded = false + + 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 introActionObserver: Observer = Observer { action -> + if (DEBUG) { + Log.d(TAG, "introActionObserver($action)") + } + action?.let { onIntroAction(it) } + } + + private val findSensorActionObserver: Observer = Observer { action -> + if (DEBUG) { + Log.d(TAG, "findSensorActionObserver($action)") + } + action?.let { onFindSensorAction(it) } + } + + private val enrollingActionObserver: Observer = Observer { action -> + if (DEBUG) { + Log.d(TAG, "enrollingActionObserver($action)") + } + action?.let { onEnrollingAction(it) } + } + + private val enrollingErrorDialogObserver: Observer = + Observer { data -> + if (DEBUG) { + Log.d(TAG, "enrollingErrorDialogObserver($data)") + } + data?.let { + FingerprintEnrollEnrollingErrorDialog().show( + supportFragmentManager, + ENROLLING_ERROR_DIALOG_TAG + ) + } + } + + private val enrollingErrorDialogActionObserver: Observer = Observer { action -> + if (DEBUG) { + Log.d(TAG, "enrollingErrorDialogActionObserver($action)") + } + action?.let { onEnrollingErrorDialogAction(it) } + } + + private val finishActionObserver: Observer = Observer { action -> + if (DEBUG) { + Log.d(TAG, "finishActionObserver($action)") + } + action?.let { onFinishAction(it) } + } + + private val chooseLockResultCallback: ActivityResultCallback = + ActivityResultCallback { result -> + onChooseOrConfirmLockResult(true /* isChooseLock */, result) + } + + private val chooseLockLauncher: ActivityResultLauncher = + registerForActivityResult(StartActivityForResult(), chooseLockResultCallback) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + viewModel.setSavedInstanceState(savedInstanceState) + autoCredentialViewModel.setCredentialModel(savedInstanceState, intent) + + // 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 + ) + if (DEBUG) { + Log.d( + TAG, "onCreate() has savedInstance:" + (savedInstanceState != null) + + ", fragment:" + fragment + ) + } + if (fragment == null) { + checkCredential() + val request: EnrollmentRequest = viewModel.getRequest() + if (request.isSkipFindSensor) { + startEnrollingFragment() + } else if (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 + } + } + + // observe LiveData + viewModel.setResultLiveData.observe(this) { + result: ActivityResult -> onSetActivityResult(result) + } + autoCredentialViewModel.generateChallengeFailedLiveData.observe(this) { + _: Boolean -> onGenerateChallengeFailed() + } + } + + private fun startFragment(fragmentClass: Class, 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( + R.anim.shared_x_axis_activity_open_enter_dynamic_color, + R.anim.shared_x_axis_activity_open_exit, + R.anim.shared_x_axis_activity_close_enter_dynamic_color, + 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 + } + viewModelProvider[FingerprintEnrollIntroViewModel::class.java].let { + // Clear ActionLiveData in FragmentViewModel to prevent getting previous action during + // recreate, like press 'Agree' then press 'back' in FingerprintEnrollFindSensor + // activity. + it.clearActionLiveData() + it.actionLiveData.observe(this, introActionObserver) + } + } + + // 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 + viewModelProvider[FingerprintEnrollProgressViewModel::class.java] + .setToken(autoCredentialViewModel.token) + attachFindSensorViewModel() + val fragmentClass: Class = 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 + } + viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java].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 + viewModelProvider[FingerprintEnrollProgressViewModel::class.java] + .setToken(autoCredentialViewModel.token) + attachEnrollingViewModel() + val fragmentClass: Class = if (viewModel.canAssumeUdfps()) { + FingerprintEnrollEnrollingUdfpsFragment::class.java + } else if (viewModel.canAssumeSfps()) { + FingerprintEnrollEnrollingSfpsFragment::class.java + } else { + FingerprintEnrollEnrollingRfpsFragment::class.java + } + startFragment(fragmentClass, ENROLLING_TAG) + } + + private fun attachEnrollingViewModel() { + viewModelProvider[FingerprintEnrollEnrollingViewModel::class.java].let { + it.clearActionLiveData() + it.actionLiveData.observe(this, enrollingActionObserver) + it.errorDialogLiveData.observe(this, enrollingErrorDialogObserver) + it.errorDialogActionLiveData.observe( + this, + enrollingErrorDialogActionObserver + ) + } + } + + private fun startFinishFragment() { + viewModel.setIsNewFingerprintAdded() + attachFinishViewModel() + if (viewModel.request.isSkipFindSensor) { + // Set page to Finish + supportFragmentManager.beginTransaction() + .setReorderingAllowed(true) + .setCustomAnimations( + R.anim.shared_x_axis_activity_open_enter_dynamic_color, + R.anim.shared_x_axis_activity_open_exit, + R.anim.shared_x_axis_activity_close_enter_dynamic_color, + 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( + R.anim.shared_x_axis_activity_open_enter_dynamic_color, + R.anim.shared_x_axis_activity_open_exit, + R.anim.shared_x_axis_activity_close_enter_dynamic_color, + 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() { + viewModelProvider[FingerprintEnrollFinishViewModel::class.java].let { + it.clearActionLiveData() + it.actionLiveData.observe(this, finishActionObserver) + } + } + + private fun onGenerateChallengeFailed() { + onSetActivityResult(ActivityResult(RESULT_CANCELED, null)) + } + + 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()) { + CREDENTIAL_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 + } + + CREDENTIAL_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 + } + + CREDENTIAL_VALID, + CREDENTIAL_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.checkNewCredentialFromActivityResult( + isChooseLock, activityResult + ) + ) { + overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out) + } else { + onSetActivityResult(activityResult) + } + } + + private fun onIntroAction(@FingerprintEnrollIntroAction action: Int) { + when (action) { + FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH -> { + onSetActivityResult( + ActivityResult(BiometricEnrollBase.RESULT_FINISHED, null) + ) + return + } + + FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL -> { + onSetActivityResult( + ActivityResult(BiometricEnrollBase.RESULT_SKIP, null) + ) + return + } + + FINGERPRINT_ENROLL_INTRO_ACTION_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 onEnrollingErrorDialogAction(@FingerprintErrorDialogAction action: Int) { + when (action) { + 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 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) + } + + 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() { + val fingerprintRepository = FeatureFactory + .getFactory(application) + .biometricsRepositoryProvider + .getFingerprintRepository(application)!! + val credentialModel = CredentialModel(intent.extras, SystemClock.elapsedRealtimeClock()) + + return MutableCreationExtras(super.defaultViewModelCreationExtras).also { + it[CHALLENGE_GENERATOR_KEY] = FingerprintChallengeGenerator(fingerprintRepository) + it[ENROLLMENT_REQUEST_KEY] = + EnrollmentRequest(intent, applicationContext, this is SetupActivity) + it[USER_ID_KEY] = credentialModel.userId + } + } + + 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) + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + viewModel.onSaveInstanceState(outState) + autoCredentialViewModel.onSaveInstanceState(outState) + } + + companion object { + private const val DEBUG = false + private const val TAG = "FingerprintEnrollmentActivity" + 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 ENROLLING_ERROR_DIALOG_TAG = "enrolling-error-dialog" + protected const val LAUNCH_CONFIRM_LOCK_ACTIVITY = 1 + } +} diff --git a/tests/uitests/Android.bp b/tests/uitests/Android.bp index d4f09a30178..407b5ecfa7b 100644 --- a/tests/uitests/Android.bp +++ b/tests/uitests/Android.bp @@ -26,7 +26,10 @@ android_test { platform_apis: true, certificate: "platform", test_suites: ["device-tests"], - srcs: ["src/**/*.java"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], libs: [ "android.test.runner", @@ -34,6 +37,7 @@ android_test { ], static_libs: [ + "androidx.test.ext.junit", "androidx.test.rules", "androidx.test.uiautomator_uiautomator", "app-helpers-core", diff --git a/tests/uitests/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivityTest.java b/tests/uitests/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivityTest.java deleted file mode 100644 index 0a84c158fd9..00000000000 --- a/tests/uitests/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivityTest.java +++ /dev/null @@ -1,622 +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 static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assume.assumeFalse; -import static org.junit.Assume.assumeTrue; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.hardware.fingerprint.FingerprintManager; -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; -import android.os.UserHandle; -import android.support.test.uiautomator.By; -import android.support.test.uiautomator.UiDevice; -import android.support.test.uiautomator.UiObject2; -import android.support.test.uiautomator.Until; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.widget.LockPatternChecker; -import com.android.internal.widget.LockPatternUtils; -import com.android.internal.widget.LockscreenCredential; -import com.android.settings.biometrics2.utils.LockScreenUtil; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.IOException; -import java.util.List; - -@RunWith(AndroidJUnit4.class) -public class FingerprintEnrollmentActivityTest { - - private static final String TAG = "FingerprintEnrollmentActivityTest"; - - private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; - private static final String ACTIVITY_CLASS_NAME = - "com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity"; - private static final String SUW_ACTIVITY_CLASS_NAME = ACTIVITY_CLASS_NAME + "$SetupActivity"; - private static final String EXTRA_IS_SETUP_FLOW = "isSetupFlow"; - private static final String EXTRA_SKIP_INTRO = "skip_intro"; - private static final String EXTRA_SKIP_FIND_SENSOR = "skip_find_sensor"; - private static final String EXTRA_PAGE_TRANSITION_TYPE = "page_transition_type"; - private static final String EXTRA_KEY_GK_PW_HANDLE = "gk_pw_handle"; - private static final String TEST_PIN = "1234"; - - private static final String DO_IT_LATER = "Do it later"; - - private static final String UDFPS_ENROLLING_TITLE = "Touch & hold the fingerprint sensor"; - private static final String SFPS_ENROLLING_TITLE = - "Lift, then touch. Move your finger slightly each time."; - private static final String RFPS_ENROLLING_TITLE = "Lift, then touch again"; - - private UiDevice mDevice; - private byte[] mToken = new byte[]{}; - private Context mContext; - private boolean mFingerprintPropCallbackLaunched; - private boolean mCanAssumeUdfps; - private boolean mCanAssumeSfps; - private String mEnrollingTitle; - - private static final int IDLE_TIMEOUT = 10000; - - @Before - public void setUp() throws InterruptedException { - mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); - mContext = InstrumentationRegistry.getContext(); - - // Stop every test if it is not a fingerprint device - assumeTrue(mContext.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_FINGERPRINT)); - - final FingerprintManager fingerprintManager = mContext.getSystemService( - FingerprintManager.class); - mFingerprintPropCallbackLaunched = false; - fingerprintManager.addAuthenticatorsRegisteredCallback( - new IFingerprintAuthenticatorsRegisteredCallback.Stub() { - @Override - public void onAllAuthenticatorsRegistered( - List list) { - mFingerprintPropCallbackLaunched = true; - - assertThat(list).isNotNull(); - assertThat(list).isNotEmpty(); - final FingerprintSensorPropertiesInternal prop = list.get(0); - mCanAssumeUdfps = prop.isAnyUdfpsType(); - mCanAssumeSfps = prop.isAnySidefpsType(); - if (mCanAssumeUdfps) { - mEnrollingTitle = UDFPS_ENROLLING_TITLE; - } else if (mCanAssumeSfps) { - mEnrollingTitle = SFPS_ENROLLING_TITLE; - } else { - mEnrollingTitle = RFPS_ENROLLING_TITLE; - } - } - }); - - for (long i = 0; i < IDLE_TIMEOUT && !mFingerprintPropCallbackLaunched; i += 100L) { - Thread.sleep(100L); - } - assertThat(mFingerprintPropCallbackLaunched).isTrue(); - - mDevice.pressHome(); - - // Stop settings before performing test - try { - mDevice.executeShellCommand("am force-stop " + SETTINGS_PACKAGE_NAME); - } catch (IOException e) { - Log.e(TAG, "Fail to stop settings app", e); - } - } - - @After - public void tearDown() throws Exception { - LockScreenUtil.resetLockscreen(TEST_PIN); - mDevice.pressHome(); - } - - @Test - public void testIntroChooseLock() { - final Intent intent = newActivityIntent(false); - mContext.startActivity(intent); - assertThat(mDevice.wait(Until.hasObject(By.text("Choose your backup screen lock method")), - IDLE_TIMEOUT)).isTrue(); - } - - private void verifyIntroPage() { - mDevice.waitForIdle(); - for (long i = 0; i < IDLE_TIMEOUT; i += 100L) { - if (mDevice.wait(Until.hasObject(By.text("More")), 50L)) { - break; - } else if (mDevice.wait(Until.hasObject(By.text("I agree")), 50L)) { - break; - } - } - - // Click more btn at most twice and the introduction should stay in the last page - UiObject2 moreBtn; - for (int i = 0; i < 2 && (moreBtn = mDevice.findObject(By.text("More"))) != null; ++i) { - moreBtn.click(); - mDevice.waitForIdle(); - mDevice.wait(Until.hasObject(By.text("More")), IDLE_TIMEOUT); - } - - assertThat(mDevice.wait(Until.hasObject(By.text("No thanks")), IDLE_TIMEOUT)).isTrue(); - assertThat(mDevice.wait(Until.hasObject(By.text("I agree")), IDLE_TIMEOUT)).isTrue(); - } - - @Test - public void testIntroWithGkPwHandle_withUdfps_clickStart() { - assumeTrue(mCanAssumeUdfps); - - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchIntroWithGkPwHandle(false); - - // Intro page - verifyIntroPage(); - final UiObject2 agreeBtn = mDevice.findObject(By.text("I agree")); - assertThat(agreeBtn).isNotNull(); - agreeBtn.click(); - - // FindUdfps page - assertThat(mDevice.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - final UiObject2 lottie = mDevice.findObject(By.res(SETTINGS_PACKAGE_NAME, - "illustration_lottie")); - assertThat(lottie).isNotNull(); - assertThat(lottie.isClickable()).isTrue(); - final UiObject2 startBtn = mDevice.findObject(By.text("Start")); - assertThat(startBtn.isClickable()).isTrue(); - startBtn.click(); - - // Enrolling page - assertThat(mDevice.wait(Until.hasObject(By.text(mEnrollingTitle)), IDLE_TIMEOUT)).isTrue(); - } - - @Test - public void testIntroWithGkPwHandle_withUdfps_clickLottie() { - assumeTrue(mCanAssumeUdfps); - - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchIntroWithGkPwHandle(false); - - // Intro page - verifyIntroPage(); - final UiObject2 agreeBtn = mDevice.findObject(By.text("I agree")); - assertThat(agreeBtn).isNotNull(); - agreeBtn.click(); - - // FindUdfps page - assertThat(mDevice.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - final UiObject2 lottie = mDevice.findObject(By.res(SETTINGS_PACKAGE_NAME, - "illustration_lottie")); - assertThat(lottie).isNotNull(); - assertThat(lottie.isClickable()).isTrue(); - final UiObject2 startBtn = mDevice.findObject(By.text("Start")); - assertThat(startBtn.isClickable()).isTrue(); - lottie.click(); - - // Enrolling page - assertThat(mDevice.wait(Until.hasObject(By.text(mEnrollingTitle)), IDLE_TIMEOUT)).isTrue(); - } - - @Test - public void testIntroWithGkPwHandle_withSfps() { - assumeTrue(mCanAssumeSfps); - - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchIntroWithGkPwHandle(false); - - // Intro page - verifyIntroPage(); - final UiObject2 agreeBtn = mDevice.findObject(By.text("I agree")); - assertThat(agreeBtn).isNotNull(); - agreeBtn.click(); - - // FindSfps page - assertThat(mDevice.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - final UiObject2 lottie = mDevice.findObject(By.res(SETTINGS_PACKAGE_NAME, - "illustration_lottie")); - assertThat(lottie).isNotNull(); - - // We don't have view which can be clicked to run to next page, stop at here. - } - - @Test - public void testIntroWithGkPwHandle_withRfps() { - assumeFalse(mCanAssumeUdfps || mCanAssumeSfps); - - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchIntroWithGkPwHandle(false); - - // Intro page - verifyIntroPage(); - final UiObject2 agreeBtn = mDevice.findObject(By.text("I agree")); - assertThat(agreeBtn).isNotNull(); - agreeBtn.click(); - - // FindRfps page - assertThat(mDevice.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - final UiObject2 lottie = mDevice.findObject(By.res(SETTINGS_PACKAGE_NAME, - "illustration_lottie")); - if (lottie == null) { - // FindSfps page shall have an animation view if no lottie view - assertThat(mDevice.findObject(By.res(SETTINGS_PACKAGE_NAME, - "fingerprint_sensor_location_animation"))).isNotNull(); - } - } - - @Test - public void testIntroWithGkPwHandle_clickNoThanksInIntroPage() { - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchIntroWithGkPwHandle(false); - - // Intro page - verifyIntroPage(); - final UiObject2 noThanksBtn = mDevice.findObject(By.text("No thanks")); - assertThat(noThanksBtn).isNotNull(); - noThanksBtn.click(); - - // Back to home - mDevice.waitForWindowUpdate("com.android.settings", IDLE_TIMEOUT); - assertThat(mDevice.findObject(By.text("No thanks"))).isNull(); - } - - @Test - public void testIntroWithGkPwHandle_clickSkipInFindSensor() { - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchIntroWithGkPwHandle(false); - - // Intro page - verifyIntroPage(); - final UiObject2 agreeBtn = mDevice.findObject(By.text("I agree")); - assertThat(agreeBtn).isNotNull(); - agreeBtn.click(); - - // FindSensor page - assertThat(mDevice.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - final UiObject2 doItLaterBtn = mDevice.findObject(By.text(DO_IT_LATER)); - assertThat(doItLaterBtn).isNotNull(); - assertThat(doItLaterBtn.isClickable()).isTrue(); - doItLaterBtn.click(); - - // Back to home - mDevice.waitForWindowUpdate("com.android.settings", IDLE_TIMEOUT); - assertThat(mDevice.findObject(By.text(DO_IT_LATER))).isNull(); - } - - @Test - public void testIntroWithGkPwHandle_clickSkipAnywayInFindFpsDialog_whenIsSuw() { - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchIntroWithGkPwHandle(true); - - // Intro page - verifyIntroPage(); - final UiObject2 agreeBtn = mDevice.findObject(By.text("I agree")); - assertThat(agreeBtn).isNotNull(); - agreeBtn.click(); - - // FindSensor page - assertThat(mDevice.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - final UiObject2 doItLaterBtn = mDevice.findObject(By.text(DO_IT_LATER)); - assertThat(doItLaterBtn).isNotNull(); - assertThat(doItLaterBtn.isClickable()).isTrue(); - doItLaterBtn.click(); - - // SkipSetupFindFpsDialog - assertThat(mDevice.wait(Until.hasObject(By.text("Skip fingerprint?")), - IDLE_TIMEOUT)).isTrue(); - final UiObject2 skipAnywayBtn = mDevice.findObject(By.text("Skip anyway")); - assertThat(skipAnywayBtn).isNotNull(); - assertThat(skipAnywayBtn.isClickable()).isTrue(); - skipAnywayBtn.click(); - - // Back to home - mDevice.waitForWindowUpdate("com.android.settings", IDLE_TIMEOUT); - assertThat(mDevice.findObject(By.text("Skip anyway"))).isNull(); - assertThat(mDevice.findObject(By.text(DO_IT_LATER))).isNull(); - } - - @Test - public void testIntroWithGkPwHandle_clickGoBackInFindFpsDialog_whenIsSuw() { - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchIntroWithGkPwHandle(true); - - // Intro page - verifyIntroPage(); - final UiObject2 agreeBtn = mDevice.findObject(By.text("I agree")); - assertThat(agreeBtn).isNotNull(); - agreeBtn.click(); - - // FindSensor page - assertThat(mDevice.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - final UiObject2 doItLaterBtn = mDevice.findObject(By.text(DO_IT_LATER)); - assertThat(doItLaterBtn).isNotNull(); - assertThat(doItLaterBtn.isClickable()).isTrue(); - doItLaterBtn.click(); - - // SkipSetupFindFpsDialog - assertThat(mDevice.wait(Until.hasObject(By.text("Skip fingerprint?")), IDLE_TIMEOUT)) - .isTrue(); - final UiObject2 goBackBtn = mDevice.findObject(By.text("Go back")); - assertThat(goBackBtn).isNotNull(); - assertThat(goBackBtn.isClickable()).isTrue(); - goBackBtn.click(); - - // FindSensor page again - assertThat(mDevice.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - } - - @Test - public void testIntroCheckPin() { - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - final Intent intent = newActivityIntent(false); - mContext.startActivity(intent); - assertThat(mDevice.wait(Until.hasObject(By.text("Enter your device PIN to continue")), - IDLE_TIMEOUT)).isTrue(); - } - - @Test - public void testEnrollingWithGkPwHandle() { - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchEnrollingWithGkPwHandle(); - - // Enrolling screen - mDevice.waitForIdle(); - assertThat(mDevice.wait(Until.hasObject(By.text(mEnrollingTitle)), IDLE_TIMEOUT)).isTrue(); - } - - @Test - public void testEnrollingIconTouchDialog_withSfps() { - assumeTrue(mCanAssumeSfps); - - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchEnrollingWithGkPwHandle(); - - // Enrolling screen - mDevice.waitForIdle(); - assertThat(mDevice.wait(Until.hasObject(By.text(mEnrollingTitle)), IDLE_TIMEOUT)).isTrue(); - - final UiObject2 lottie = mDevice.findObject(By.res(SETTINGS_PACKAGE_NAME, - "illustration_lottie")); - assertThat(lottie).isNotNull(); - - lottie.click(); - lottie.click(); - lottie.click(); - - // IconTouchDialog - mDevice.waitForIdle(); - assertThat(mDevice.wait(Until.hasObject(By.text("Touch the sensor instead")), IDLE_TIMEOUT)) - .isTrue(); - final UiObject2 okButton = mDevice.findObject(By.text("OK")); - assertThat(okButton).isNotNull(); - - okButton.click(); - - // Enrolling screen again - mDevice.waitForIdle(); - assertThat(mDevice.wait(Until.hasObject(By.text(mEnrollingTitle)), IDLE_TIMEOUT)).isTrue(); - } - - @Test - public void testEnrollingIconTouchDialog_withRfps() { - assumeFalse(mCanAssumeUdfps || mCanAssumeSfps); - - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchEnrollingWithGkPwHandle(); - - // Enrolling screen - mDevice.waitForIdle(); - assertThat(mDevice.wait(Until.hasObject(By.text(mEnrollingTitle)), IDLE_TIMEOUT)).isTrue(); - - final UiObject2 lottie = mDevice.findObject(By.res(SETTINGS_PACKAGE_NAME, - "fingerprint_progress_bar")); - assertThat(lottie).isNotNull(); - - lottie.click(); - lottie.click(); - lottie.click(); - - // IconTouchDialog - mDevice.waitForIdle(); - assertThat(mDevice.wait(Until.hasObject(By.text("Whoops, that\u2019s not the sensor")), - IDLE_TIMEOUT)).isTrue(); - final UiObject2 okButton = mDevice.findObject(By.text("OK")); - assertThat(okButton).isNotNull(); - - okButton.click(); - - // Enrolling screen again - mDevice.waitForIdle(); - assertThat(mDevice.wait(Until.hasObject(By.text(mEnrollingTitle)), IDLE_TIMEOUT)).isTrue(); - } - - @Test - public void testFindUdfpsWithGkPwHandle_clickStart() { - assumeTrue(mCanAssumeUdfps); - - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchFindSensorWithGkPwHandle(); - - // FindUdfps page - assertThat(mDevice.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - final UiObject2 lottie = mDevice.findObject(By.res(SETTINGS_PACKAGE_NAME, - "illustration_lottie")); - assertThat(lottie).isNotNull(); - assertThat(lottie.isClickable()).isTrue(); - final UiObject2 startBtn = mDevice.findObject(By.text("Start")); - assertThat(startBtn.isClickable()).isTrue(); - startBtn.click(); - - // Enrolling page - assertThat(mDevice.wait(Until.hasObject(By.text(mEnrollingTitle)), IDLE_TIMEOUT)).isTrue(); - } - - @Test - public void testFindUdfpsWithGkPwHandle_clickLottie() { - assumeTrue(mCanAssumeUdfps); - - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchFindSensorWithGkPwHandle(); - - // FindUdfps page - assertThat(mDevice.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - final UiObject2 lottie = mDevice.findObject(By.res(SETTINGS_PACKAGE_NAME, - "illustration_lottie")); - assertThat(lottie).isNotNull(); - assertThat(lottie.isClickable()).isTrue(); - final UiObject2 startBtn = mDevice.findObject(By.text("Start")); - assertThat(startBtn.isClickable()).isTrue(); - lottie.click(); - - // Enrolling page - assertThat(mDevice.wait(Until.hasObject(By.text(mEnrollingTitle)), IDLE_TIMEOUT)).isTrue(); - } - - @Test - public void testFindSfpsWithGkPwHandle() { - assumeTrue(mCanAssumeSfps); - - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchFindSensorWithGkPwHandle(); - - // FindSfps page - assertThat(mDevice.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - final UiObject2 lottie = mDevice.findObject(By.res(SETTINGS_PACKAGE_NAME, - "illustration_lottie")); - assertThat(lottie).isNotNull(); - - // We don't have view which can be clicked to run to next page, stop at here. - } - - @Test - public void testFindRfpsWithGkPwHandle() { - assumeFalse(mCanAssumeUdfps || mCanAssumeSfps); - - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchFindSensorWithGkPwHandle(); - - // FindRfps page - assertThat(mDevice.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - final UiObject2 lottie = mDevice.findObject(By.res(SETTINGS_PACKAGE_NAME, - "illustration_lottie")); - if (lottie == null) { - // FindSfps page shall have an animation view if no lottie view - assertThat(mDevice.findObject(By.res(SETTINGS_PACKAGE_NAME, - "fingerprint_sensor_location_animation"))).isNotNull(); - } - } - - - @Test - public void testFindSensorWithGkPwHandle_clickSkipInFindSensor() { - LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true); - - launchFindSensorWithGkPwHandle(); - - // FindSensor page - assertThat(mDevice.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - final UiObject2 doItLaterBtn = mDevice.findObject(By.text(DO_IT_LATER)); - assertThat(doItLaterBtn).isNotNull(); - assertThat(doItLaterBtn.isClickable()).isTrue(); - doItLaterBtn.click(); - - // Back to home - mDevice.waitForWindowUpdate("com.android.settings", IDLE_TIMEOUT); - assertThat(mDevice.wait(Until.gone(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue(); - } - - private void launchIntroWithGkPwHandle(boolean isSuw) { - LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); - final LockscreenCredential lockscreenCredential = LockscreenCredential.createPin(TEST_PIN); - final int userId = UserHandle.myUserId(); - final LockPatternChecker.OnVerifyCallback onVerifyCallback = (response, timeoutMs) -> { - final Intent intent = newActivityIntent(isSuw); - intent.putExtra(EXTRA_KEY_GK_PW_HANDLE, response.getGatekeeperPasswordHandle()); - mContext.startActivity(intent); - }; - LockPatternChecker.verifyCredential(lockPatternUtils, lockscreenCredential, - userId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE, onVerifyCallback); - } - - private void launchFindSensorWithGkPwHandle() { - LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); - final LockscreenCredential lockscreenCredential = LockscreenCredential.createPin(TEST_PIN); - final int userId = UserHandle.myUserId(); - final LockPatternChecker.OnVerifyCallback onVerifyCallback = (response, timeoutMs) -> { - final Intent intent = newActivityIntent(false); - intent.putExtra(EXTRA_SKIP_INTRO, true); - intent.putExtra(EXTRA_KEY_GK_PW_HANDLE, response.getGatekeeperPasswordHandle()); - mContext.startActivity(intent); - }; - LockPatternChecker.verifyCredential(lockPatternUtils, lockscreenCredential, - userId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE, onVerifyCallback); - } - - private void launchEnrollingWithGkPwHandle() { - LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); - final LockscreenCredential lockscreenCredential = LockscreenCredential.createPin(TEST_PIN); - final int userId = UserHandle.myUserId(); - final LockPatternChecker.OnVerifyCallback onVerifyCallback = (response, timeoutMs) -> { - final Intent intent = newActivityIntent(false); - intent.putExtra(EXTRA_SKIP_FIND_SENSOR, true); - intent.putExtra(EXTRA_KEY_GK_PW_HANDLE, response.getGatekeeperPasswordHandle()); - mContext.startActivity(intent); - }; - LockPatternChecker.verifyCredential(lockPatternUtils, lockscreenCredential, - userId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE, onVerifyCallback); - } - - @NonNull - private Intent newActivityIntent(boolean isSuw) { - Intent intent = new Intent(); - intent.setClassName(SETTINGS_PACKAGE_NAME, - isSuw ? SUW_ACTIVITY_CLASS_NAME : ACTIVITY_CLASS_NAME); - if (isSuw) { - intent.putExtra(EXTRA_IS_SETUP_FLOW, true); - } - intent.putExtra(EXTRA_PAGE_TRANSITION_TYPE, 1); - intent.putExtra(Intent.EXTRA_USER_ID, mContext.getUserId()); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - return intent; - } -} diff --git a/tests/uitests/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivityTest.kt b/tests/uitests/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivityTest.kt new file mode 100644 index 00000000000..49c5ac94411 --- /dev/null +++ b/tests/uitests/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivityTest.kt @@ -0,0 +1,632 @@ +/* + * 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.content.Intent +import android.content.pm.PackageManager.FEATURE_FINGERPRINT +import android.hardware.fingerprint.FingerprintManager +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback +import android.os.UserHandle +import android.support.test.uiautomator.By +import android.support.test.uiautomator.UiDevice +import android.support.test.uiautomator.UiObject2 +import android.support.test.uiautomator.Until +import android.util.Log +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.android.internal.widget.LockPatternChecker +import com.android.internal.widget.LockPatternUtils +import com.android.internal.widget.LockscreenCredential +import com.android.internal.widget.VerifyCredentialResponse +import com.android.settings.biometrics2.utils.LockScreenUtil +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.io.IOException + +@RunWith(AndroidJUnit4::class) +class FingerprintEnrollmentActivityTest { + + private val context: Context by lazy { + InstrumentationRegistry.getInstrumentation().context + } + + private val fingerprintManager: FingerprintManager by lazy { + context.getSystemService(FingerprintManager::class.java)!! + } + + private var fingerprintPropCallbackLaunched = false + private var canAssumeUdfps = false + private var canAssumeSfps = false + private var enrollingPageTitle: String = "" + + private val device: UiDevice by lazy { + UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + } + + @Before + @Throws(InterruptedException::class) + fun setUp() { + // Stop every test if it is not a fingerprint device + Assume.assumeTrue(context.packageManager.hasSystemFeature(FEATURE_FINGERPRINT)) + + fingerprintPropCallbackLaunched = false + fingerprintManager.addAuthenticatorsRegisteredCallback( + object : IFingerprintAuthenticatorsRegisteredCallback.Stub() { + override fun onAllAuthenticatorsRegistered( + list: List + ) { + fingerprintPropCallbackLaunched = true + assertThat(list).isNotNull() + assertThat(list).isNotEmpty() + val prop = list[0] + canAssumeUdfps = prop.isAnyUdfpsType + canAssumeSfps = prop.isAnySidefpsType + enrollingPageTitle = if (canAssumeUdfps) { + UDFPS_ENROLLING_TITLE + } else if (canAssumeSfps) { + SFPS_ENROLLING_TITLE + } else { + RFPS_ENROLLING_TITLE + } + } + }) + var i: Long = 0 + while (i < IDLE_TIMEOUT && !fingerprintPropCallbackLaunched) { + Thread.sleep(100L) + i += 100L + } + assertThat(fingerprintPropCallbackLaunched).isTrue() + device.pressHome() + + // Stop settings before performing test + try { + device.executeShellCommand("am force-stop $SETTINGS_PACKAGE_NAME") + } catch (e: IOException) { + Log.e(TAG, "Fail to stop settings app", e) + } + } + + @After + @Throws(Exception::class) + fun tearDown() { + LockScreenUtil.resetLockscreen(TEST_PIN) + device.pressHome() + } + + @Test + fun testIntroChooseLock() { + val intent = newActivityIntent(false) + context.startActivity(intent) + assertThat( + device.wait( + Until.hasObject(By.text("Choose your backup screen lock method")), + IDLE_TIMEOUT + ) + ).isTrue() + } + + private fun verifyIntroPage() { + device.waitForIdle() + run { + var i: Long = 0 + while (i < IDLE_TIMEOUT) { + if (device.wait(Until.hasObject(By.text("More")), 50L)) { + break + } else if (device.wait(Until.hasObject(By.text("I agree")), 50L)) { + break + } + i += 100L + } + } + + // Click more btn at most twice and the introduction should stay in the last page + var moreBtn: UiObject2? = null + var i = 0 + while (i < 2 && device.findObject(By.text("More")).also { moreBtn = it } != null) { + moreBtn!!.click() + device.waitForIdle() + device.wait(Until.hasObject(By.text("More")), IDLE_TIMEOUT) + ++i + } + assertThat(device.wait(Until.hasObject(By.text("No thanks")), IDLE_TIMEOUT)).isTrue() + assertThat(device.wait(Until.hasObject(By.text("I agree")), IDLE_TIMEOUT)).isTrue() + } + + @Test + fun testIntroWithGkPwHandle_withUdfps_clickStart() { + Assume.assumeTrue(canAssumeUdfps) + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchIntroWithGkPwHandle(false) + + // Intro page + verifyIntroPage() + val agreeBtn = device.findObject(By.text("I agree")) + assertThat(agreeBtn).isNotNull() + agreeBtn.click() + + // FindUdfps page + assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + val lottie = device.findObject( + By.res(SETTINGS_PACKAGE_NAME, "illustration_lottie") + ) + assertThat(lottie).isNotNull() + assertThat(lottie.isClickable).isTrue() + val startBtn = device.findObject(By.text("Start")) + assertThat(startBtn.isClickable).isTrue() + startBtn.click() + + // Enrolling page + assertThat(device.wait(Until.hasObject(By.text(enrollingPageTitle)), IDLE_TIMEOUT)).isTrue() + } + + @Test + fun testIntroWithGkPwHandle_withUdfps_clickLottie() { + Assume.assumeTrue(canAssumeUdfps) + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchIntroWithGkPwHandle(false) + + // Intro page + verifyIntroPage() + val agreeBtn = device.findObject(By.text("I agree")) + assertThat(agreeBtn).isNotNull() + agreeBtn.click() + + // FindUdfps page + assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + val lottie = device.findObject(By.res(SETTINGS_PACKAGE_NAME, "illustration_lottie")) + assertThat(lottie).isNotNull() + assertThat(lottie.isClickable).isTrue() + val startBtn = device.findObject(By.text("Start")) + assertThat(startBtn.isClickable).isTrue() + lottie.click() + + // Enrolling page + assertThat(device.wait(Until.hasObject(By.text(enrollingPageTitle)), IDLE_TIMEOUT)).isTrue() + } + + @Test + fun testIntroWithGkPwHandle_withSfps() { + Assume.assumeTrue(canAssumeSfps) + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchIntroWithGkPwHandle(false) + + // Intro page + verifyIntroPage() + val agreeBtn = device.findObject(By.text("I agree")) + assertThat(agreeBtn).isNotNull() + agreeBtn.click() + + // FindSfps page + assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + val lottie = device.findObject( + By.res(SETTINGS_PACKAGE_NAME,"illustration_lottie") + ) + assertThat(lottie).isNotNull() + + // We don't have view which can be clicked to run to next page, stop at here. + } + + @Test + fun testIntroWithGkPwHandle_withRfps() { + Assume.assumeFalse(canAssumeUdfps || canAssumeSfps) + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchIntroWithGkPwHandle(false) + + // Intro page + verifyIntroPage() + val agreeBtn = device.findObject(By.text("I agree")) + assertThat(agreeBtn).isNotNull() + agreeBtn.click() + + // FindRfps page + assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + val lottie = device.findObject( + By.res(SETTINGS_PACKAGE_NAME, "illustration_lottie") + ) + if (lottie == null) { + // FindSfps page shall have an animation view if no lottie view + assertThat( + device.findObject( + By.res(SETTINGS_PACKAGE_NAME, "fingerprint_sensor_location_animation") + ) + ).isNotNull() + } + } + + @Test + fun testIntroWithGkPwHandle_clickNoThanksInIntroPage() { + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchIntroWithGkPwHandle(false) + + // Intro page + verifyIntroPage() + val noThanksBtn = device.findObject(By.text("No thanks")) + assertThat(noThanksBtn).isNotNull() + noThanksBtn.click() + + // Back to home + device.waitForWindowUpdate(SETTINGS_PACKAGE_NAME, IDLE_TIMEOUT) + assertThat(device.findObject(By.text("No thanks"))).isNull() + } + + @Test + fun testIntroWithGkPwHandle_clickSkipInFindSensor() { + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchIntroWithGkPwHandle(false) + + // Intro page + verifyIntroPage() + val agreeBtn = device.findObject(By.text("I agree")) + assertThat(agreeBtn).isNotNull() + agreeBtn.click() + + // FindSensor page + assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + val doItLaterBtn = device.findObject(By.text(DO_IT_LATER)) + assertThat(doItLaterBtn).isNotNull() + assertThat(doItLaterBtn.isClickable).isTrue() + doItLaterBtn.click() + + // Back to home + device.waitForWindowUpdate(SETTINGS_PACKAGE_NAME, IDLE_TIMEOUT) + assertThat(device.findObject(By.text(DO_IT_LATER))).isNull() + } + + @Test + fun testIntroWithGkPwHandle_clickSkipAnywayInFindFpsDialog_whenIsSuw() { + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchIntroWithGkPwHandle(true) + + // Intro page + verifyIntroPage() + val agreeBtn = device.findObject(By.text("I agree")) + assertThat(agreeBtn).isNotNull() + agreeBtn.click() + + // FindSensor page + assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + val doItLaterBtn = device.findObject(By.text(DO_IT_LATER)) + assertThat(doItLaterBtn).isNotNull() + assertThat(doItLaterBtn.isClickable).isTrue() + doItLaterBtn.click() + + // SkipSetupFindFpsDialog + assertThat(device.wait(Until.hasObject(By.text("Skip fingerprint?")), IDLE_TIMEOUT)).isTrue() + val skipAnywayBtn = device.findObject(By.text("Skip anyway")) + assertThat(skipAnywayBtn).isNotNull() + assertThat(skipAnywayBtn.isClickable).isTrue() + skipAnywayBtn.click() + + // Back to home + device.waitForWindowUpdate(SETTINGS_PACKAGE_NAME, IDLE_TIMEOUT) + assertThat(device.findObject(By.text("Skip anyway"))).isNull() + assertThat(device.findObject(By.text(DO_IT_LATER))).isNull() + } + + @Test + fun testIntroWithGkPwHandle_clickGoBackInFindFpsDialog_whenIsSuw() { + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchIntroWithGkPwHandle(true) + + // Intro page + verifyIntroPage() + val agreeBtn = device.findObject(By.text("I agree")) + assertThat(agreeBtn).isNotNull() + agreeBtn.click() + + // FindSensor page + assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + val doItLaterBtn = device.findObject(By.text(DO_IT_LATER)) + assertThat(doItLaterBtn).isNotNull() + assertThat(doItLaterBtn.isClickable).isTrue() + doItLaterBtn.click() + + // SkipSetupFindFpsDialog + assertThat(device.wait(Until.hasObject(By.text("Skip fingerprint?")), IDLE_TIMEOUT)).isTrue() + val goBackBtn = device.findObject(By.text("Go back")) + assertThat(goBackBtn).isNotNull() + assertThat(goBackBtn.isClickable).isTrue() + goBackBtn.click() + + // FindSensor page again + assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + } + + @Test + fun testIntroCheckPin() { + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + val intent = newActivityIntent(false) + context.startActivity(intent) + assertThat( + device.wait( + Until.hasObject(By.text("Enter your device PIN to continue")), + IDLE_TIMEOUT + ) + ).isTrue() + } + + @Test + fun testEnrollingWithGkPwHandle() { + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchEnrollingWithGkPwHandle() + + // Enrolling screen + device.waitForIdle() + assertThat(device.wait(Until.hasObject(By.text(enrollingPageTitle)), IDLE_TIMEOUT)).isTrue() + } + + @Test + fun testEnrollingIconTouchDialog_withSfps() { + Assume.assumeTrue(canAssumeSfps) + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchEnrollingWithGkPwHandle() + + // Enrolling screen + device.waitForIdle() + assertThat(device.wait(Until.hasObject(By.text(enrollingPageTitle)), IDLE_TIMEOUT)).isTrue() + val lottie = device.findObject( + By.res(SETTINGS_PACKAGE_NAME, "illustration_lottie") + ) + assertThat(lottie).isNotNull() + lottie.click() + lottie.click() + lottie.click() + + // IconTouchDialog + device.waitForIdle() + assertThat( + device.wait( + Until.hasObject(By.text("Touch the sensor instead")), + IDLE_TIMEOUT + ) + ) + .isTrue() + val okButton = device.findObject(By.text("OK")) + assertThat(okButton).isNotNull() + okButton.click() + + // Enrolling screen again + device.waitForIdle() + assertThat(device.wait(Until.hasObject(By.text(enrollingPageTitle)), IDLE_TIMEOUT)).isTrue() + } + + @Test + fun testEnrollingIconTouchDialog_withRfps() { + Assume.assumeFalse(canAssumeUdfps || canAssumeSfps) + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchEnrollingWithGkPwHandle() + + // Enrolling screen + device.waitForIdle() + assertThat(device.wait(Until.hasObject(By.text(enrollingPageTitle)), IDLE_TIMEOUT)).isTrue() + val lottie = device.findObject( + By.res(SETTINGS_PACKAGE_NAME, "fingerprint_progress_bar") + ) + assertThat(lottie).isNotNull() + lottie.click() + lottie.click() + lottie.click() + + // IconTouchDialog + device.waitForIdle() + assertThat( + device.wait( + Until.hasObject(By.text("Whoops, that\u2019s not the sensor")), + IDLE_TIMEOUT + ) + ).isTrue() + val okButton = device.findObject(By.text("OK")) + assertThat(okButton).isNotNull() + okButton.click() + + // Enrolling screen again + device.waitForIdle() + assertThat(device.wait(Until.hasObject(By.text(enrollingPageTitle)), IDLE_TIMEOUT)).isTrue() + } + + @Test + fun testFindUdfpsWithGkPwHandle_clickStart() { + Assume.assumeTrue(canAssumeUdfps) + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchFindSensorWithGkPwHandle() + + // FindUdfps page + assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + val lottie = device.findObject( + By.res(SETTINGS_PACKAGE_NAME, "illustration_lottie") + ) + assertThat(lottie).isNotNull() + assertThat(lottie.isClickable).isTrue() + val startBtn = device.findObject(By.text("Start")) + assertThat(startBtn.isClickable).isTrue() + startBtn.click() + + // Enrolling page + assertThat(device.wait(Until.hasObject(By.text(enrollingPageTitle)), IDLE_TIMEOUT)).isTrue() + } + + @Test + fun testFindUdfpsWithGkPwHandle_clickLottie() { + Assume.assumeTrue(canAssumeUdfps) + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchFindSensorWithGkPwHandle() + + // FindUdfps page + assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + val lottie = device.findObject( + By.res(SETTINGS_PACKAGE_NAME, "illustration_lottie") + ) + assertThat(lottie).isNotNull() + assertThat(lottie.isClickable).isTrue() + val startBtn = device.findObject(By.text("Start")) + assertThat(startBtn.isClickable).isTrue() + lottie.click() + + // Enrolling page + assertThat(device.wait(Until.hasObject(By.text(enrollingPageTitle)), IDLE_TIMEOUT)).isTrue() + } + + @Test + fun testFindSfpsWithGkPwHandle() { + Assume.assumeTrue(canAssumeSfps) + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchFindSensorWithGkPwHandle() + + // FindSfps page + assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + val lottie = device.findObject( + By.res(SETTINGS_PACKAGE_NAME, "illustration_lottie") + ) + assertThat(lottie).isNotNull() + + // We don't have view which can be clicked to run to next page, stop at here. + } + + @Test + fun testFindRfpsWithGkPwHandle() { + Assume.assumeFalse(canAssumeUdfps || canAssumeSfps) + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchFindSensorWithGkPwHandle() + + // FindRfps page + assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + val lottie = device.findObject( + By.res( + SETTINGS_PACKAGE_NAME, + "illustration_lottie" + ) + ) + if (lottie == null) { + // FindSfps page shall have an animation view if no lottie view + assertThat( + device.findObject( + By.res( + SETTINGS_PACKAGE_NAME, + "fingerprint_sensor_location_animation" + ) + ) + ).isNotNull() + } + } + + @Test + fun testFindSensorWithGkPwHandle_clickSkipInFindSensor() { + LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, TEST_PIN, true) + launchFindSensorWithGkPwHandle() + + // FindSensor page + assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + val doItLaterBtn = device.findObject(By.text(DO_IT_LATER)) + assertThat(doItLaterBtn).isNotNull() + assertThat(doItLaterBtn.isClickable).isTrue() + doItLaterBtn.click() + + // Back to home + device.waitForWindowUpdate(SETTINGS_PACKAGE_NAME, IDLE_TIMEOUT) + assertThat(device.wait(Until.gone(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue() + } + + private fun launchIntroWithGkPwHandle(isSuw: Boolean) { + val lockPatternUtils = LockPatternUtils(context) + val lockscreenCredential = LockscreenCredential.createPin(TEST_PIN) + val userId = UserHandle.myUserId() + val onVerifyCallback = + LockPatternChecker.OnVerifyCallback { response: VerifyCredentialResponse, _: Int -> + val intent = newActivityIntent(isSuw) + intent.putExtra(EXTRA_KEY_GK_PW_HANDLE, response.gatekeeperPasswordHandle) + context.startActivity(intent) + } + LockPatternChecker.verifyCredential( + lockPatternUtils, lockscreenCredential, + userId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE, onVerifyCallback + ) + } + + private fun launchFindSensorWithGkPwHandle() { + val lockPatternUtils = LockPatternUtils(context) + val lockscreenCredential = LockscreenCredential.createPin(TEST_PIN) + val userId = UserHandle.myUserId() + val onVerifyCallback = + LockPatternChecker.OnVerifyCallback { response: VerifyCredentialResponse, _: Int -> + val intent = newActivityIntent(false) + intent.putExtra(EXTRA_SKIP_INTRO, true) + intent.putExtra(EXTRA_KEY_GK_PW_HANDLE, response.gatekeeperPasswordHandle) + context.startActivity(intent) + } + LockPatternChecker.verifyCredential( + lockPatternUtils, lockscreenCredential, + userId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE, onVerifyCallback + ) + } + + private fun launchEnrollingWithGkPwHandle() { + val lockPatternUtils = LockPatternUtils(context) + val lockscreenCredential = LockscreenCredential.createPin(TEST_PIN) + val userId = UserHandle.myUserId() + val onVerifyCallback = + LockPatternChecker.OnVerifyCallback { response: VerifyCredentialResponse, _: Int -> + val intent = newActivityIntent(false) + intent.putExtra(EXTRA_SKIP_FIND_SENSOR, true) + intent.putExtra(EXTRA_KEY_GK_PW_HANDLE, response.gatekeeperPasswordHandle) + context.startActivity(intent) + } + LockPatternChecker.verifyCredential( + lockPatternUtils, lockscreenCredential, + userId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE, onVerifyCallback + ) + } + + private fun newActivityIntent(isSuw: Boolean): Intent { + val intent = Intent() + intent.setClassName( + SETTINGS_PACKAGE_NAME, + if (isSuw) SUW_ACTIVITY_CLASS_NAME else ACTIVITY_CLASS_NAME + ) + if (isSuw) { + intent.putExtra(EXTRA_IS_SETUP_FLOW, true) + } + intent.putExtra(EXTRA_PAGE_TRANSITION_TYPE, 1) + intent.putExtra(Intent.EXTRA_USER_ID, context.userId) + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + return intent + } + + companion object { + private const val TAG = "FingerprintEnrollmentActivityTest" + const val SETTINGS_PACKAGE_NAME = "com.android.settings" + private const val ACTIVITY_CLASS_NAME = + "com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity" + private const val SUW_ACTIVITY_CLASS_NAME = "$ACTIVITY_CLASS_NAME\$SetupActivity" + private const val EXTRA_IS_SETUP_FLOW = "isSetupFlow" + private const val EXTRA_SKIP_INTRO = "skip_intro" + private const val EXTRA_SKIP_FIND_SENSOR = "skip_find_sensor" + private const val EXTRA_PAGE_TRANSITION_TYPE = "page_transition_type" + private const val EXTRA_KEY_GK_PW_HANDLE = "gk_pw_handle" + private const val TEST_PIN = "1234" + private const val DO_IT_LATER = "Do it later" + private const val UDFPS_ENROLLING_TITLE = "Touch & hold the fingerprint sensor" + private const val SFPS_ENROLLING_TITLE = + "Lift, then touch. Move your finger slightly each time." + private const val RFPS_ENROLLING_TITLE = "Lift, then touch again" + private const val IDLE_TIMEOUT = 10000L + } +}