From 75450f79736ebc6d2865e3867d92111b8fd41550 Mon Sep 17 00:00:00 2001 From: Milton Wu Date: Tue, 20 Jun 2023 12:36:11 +0800 Subject: [PATCH] [BiometricsV2] Refactor FindSfpsFragment Refactor FingerprintEnrollFindSfpsFragment to kotlin and add bindView() method for it Bug: 286197831 Test: atest FingerprintEnrollmentActivityTest Test: Manually test enrollment as Side fingerpint device Change-Id: If1c7d95e78c5be237f05209afa3ffc0b4c444c61 --- .../FingerprintEnrollFindSfpsFragment.java | 286 ---------------- .../view/FingerprintEnrollFindSfpsFragment.kt | 310 ++++++++++++++++++ 2 files changed, 310 insertions(+), 286 deletions(-) delete mode 100644 src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java create mode 100644 src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.kt diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java deleted file mode 100644 index 75207cad630..00000000000 --- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java +++ /dev/null @@ -1,286 +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 android.hardware.fingerprint.FingerprintManager.ENROLL_FIND_SENSOR; - -import android.app.Activity; -import android.content.Context; -import android.hardware.fingerprint.FingerprintManager; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Surface; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RawRes; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModelProvider; - -import com.android.settings.R; -import com.android.settings.biometrics2.ui.model.EnrollmentProgress; -import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage; -import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel; -import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel; -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel; -import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel; -import com.android.settingslib.widget.LottieColorUtils; - -import com.airbnb.lottie.LottieAnimationView; -import com.google.android.setupcompat.template.FooterBarMixin; -import com.google.android.setupcompat.template.FooterButton; -import com.google.android.setupdesign.GlifLayout; - -/** - * Fragment explaining the side fingerprint sensor location for fingerprint enrollment. - * It interacts with ProgressViewModel, FoldCallback (for different lottie), and - * LottieAnimationView. - *
- | Has                 | UDFPS | SFPS | Other (Rear FPS) |
- |---------------------|-------|------|------------------|
- | Primary button      | Yes   | No   | No               |
- | Illustration Lottie | Yes   | Yes  | No               |
- | Animation           | No    | No   | Depend on layout |
- | Progress ViewModel  | No    | Yes  | Yes              |
- | Orientation detect  | No    | Yes  | No               |
- | Foldable detect     | No    | Yes  | No               |
- 
- */ -public class FingerprintEnrollFindSfpsFragment extends Fragment { - - private static final boolean DEBUG = false; - private static final String TAG = "FingerprintEnrollFindSfpsFragment"; - - private FingerprintEnrollFindSensorViewModel mViewModel; - private FingerprintEnrollProgressViewModel mProgressViewModel; - private DeviceRotationViewModel mRotationViewModel; - private DeviceFoldedViewModel mFoldedViewModel; - - private GlifLayout mView; - private FooterBarMixin mFooterBarMixin; - private final OnClickListener mOnSkipClickListener = (v) -> mViewModel.onSkipButtonClick(); - private LottieAnimationView mIllustrationLottie; - @Surface.Rotation private int mAnimationRotation = -1; - - private final Observer mRotationObserver = rotation -> { - if (DEBUG) { - Log.d(TAG, "rotationObserver " + rotation); - } - if (rotation != null) { - onRotationChanged(rotation); - } - }; - - private final Observer mProgressObserver = progress -> { - if (DEBUG) { - Log.d(TAG, "mProgressObserver(" + progress + ")"); - } - if (progress != null && !progress.isInitialStep()) { - stopLookingForFingerprint(true); - } - }; - - private final Observer mLastCancelMessageObserver = errorMessage -> { - if (DEBUG) { - Log.d(TAG, "mLastCancelMessageObserver(" + errorMessage + ")"); - } - if (errorMessage != null) { - onLastCancelMessage(errorMessage); - } - }; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - final Context context = inflater.getContext(); - mView = (GlifLayout) inflater.inflate(R.layout.sfps_enroll_find_sensor_layout, container, - false); - mIllustrationLottie = mView.findViewById(R.id.illustration_lottie); - mFooterBarMixin = mView.getMixin(FooterBarMixin.class); - mFooterBarMixin.setSecondaryButton( - new FooterButton.Builder(context) - .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip) - .setButtonType(FooterButton.ButtonType.SKIP) - .setTheme(R.style.SudGlifButton_Secondary) - .build() - ); - return mView; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - final Activity activity = getActivity(); - final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, mView); - glifLayoutHelper.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title); - glifLayoutHelper.setDescriptionText( - getText(R.string.security_settings_sfps_enroll_find_sensor_message)); - mFooterBarMixin.getSecondaryButton().setOnClickListener(mOnSkipClickListener); - } - - @Override - public void onStart() { - super.onStart(); - - final boolean isEnrolling = mProgressViewModel.isEnrolling(); - if (DEBUG) { - Log.d(TAG, "onStart(), isEnrolling:" + isEnrolling); - } - if (!isEnrolling) { - startLookingForFingerprint(); - } - } - - @Override - public void onResume() { - super.onResume(); - final LiveData rotationLiveData = mRotationViewModel.getLiveData(); - playLottieAnimation(rotationLiveData.getValue()); - rotationLiveData.observe(this, mRotationObserver); - } - - @Override - public void onPause() { - mRotationViewModel.getLiveData().removeObserver(mRotationObserver); - super.onPause(); - } - - @Override - public void onStop() { - super.onStop(); - final boolean isEnrolling = mProgressViewModel.isEnrolling(); - if (DEBUG) { - Log.d(TAG, "onStop(), isEnrolling:" + isEnrolling); - } - if (isEnrolling) { - stopLookingForFingerprint(false); - } - } - - private void startLookingForFingerprint() { - if (mProgressViewModel.isEnrolling()) { - Log.d(TAG, "startLookingForFingerprint(), failed because isEnrolling is true before" - + " starting"); - return; - } - - mProgressViewModel.clearProgressLiveData(); - mProgressViewModel.getProgressLiveData().observe(this, mProgressObserver); - final boolean startResult = mProgressViewModel.startEnrollment(ENROLL_FIND_SENSOR); - if (!startResult) { - Log.e(TAG, "startLookingForFingerprint(), failed to start enrollment"); - } - } - - private void stopLookingForFingerprint(boolean waitForLastCancelErrMsg) { - if (!mProgressViewModel.isEnrolling()) { - Log.d(TAG, "stopLookingForFingerprint(), failed because isEnrolling is false before" - + " stopping"); - return; - } - - if (waitForLastCancelErrMsg) { - mProgressViewModel.clearErrorMessageLiveData(); // Prevent got previous error message - mProgressViewModel.getErrorMessageLiveData().observe(this, - mLastCancelMessageObserver); - } - - mProgressViewModel.getProgressLiveData().removeObserver(mProgressObserver); - final boolean cancelResult = mProgressViewModel.cancelEnrollment(); - if (!cancelResult) { - Log.e(TAG, "stopLookingForFingerprint(), failed to cancel enrollment"); - } - } - - private void onRotationChanged(@Surface.Rotation int newRotation) { - if (DEBUG) { - Log.d(TAG, "onRotationChanged() from " + mAnimationRotation + " to " + newRotation); - } - if ((newRotation + 2) % 4 == mAnimationRotation) { - // Fragment not changed, we just need to play correct rotation animation - playLottieAnimation(newRotation); - } else if (newRotation % 2 != mAnimationRotation % 2) { - // Fragment is going to be recreated, just stopLookingForFingerprint() here. - stopLookingForFingerprint(true); - } - } - - private void onLastCancelMessage(@NonNull EnrollmentStatusMessage errorMessage) { - if (errorMessage.getMsgId() == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { - final EnrollmentProgress progress = mProgressViewModel.getProgressLiveData().getValue(); - mProgressViewModel.clearProgressLiveData(); - mProgressViewModel.getErrorMessageLiveData().removeObserver(mLastCancelMessageObserver); - if (progress != null && !progress.isInitialStep()) { - mViewModel.onStartButtonClick(); - } - } else { - Log.e(TAG, "mErrorMessageObserver(" + errorMessage + ")"); - } - } - - private void playLottieAnimation(@Surface.Rotation int rotation) { - @RawRes final int animationRawRes = getSfpsLottieAnimationRawRes(rotation); - if (DEBUG) { - Log.d(TAG, "play lottie animation " + animationRawRes - + ", previous rotation:" + mAnimationRotation + ", new rotation:" + rotation); - } - - mAnimationRotation = rotation; - mIllustrationLottie.setAnimation(animationRawRes); - LottieColorUtils.applyDynamicColors(getActivity(), mIllustrationLottie); - mIllustrationLottie.setVisibility(View.VISIBLE); - mIllustrationLottie.playAnimation(); - } - - @RawRes - private int getSfpsLottieAnimationRawRes(@Surface.Rotation int rotation) { - final boolean isFolded = !Boolean.FALSE.equals(mFoldedViewModel.getLiveData().getValue()); - switch (rotation) { - case Surface.ROTATION_90: - return isFolded ? R.raw.fingerprint_edu_lottie_folded_top_left - : R.raw.fingerprint_edu_lottie_portrait_top_left; - case Surface.ROTATION_180 : - return isFolded ? R.raw.fingerprint_edu_lottie_folded_bottom_left - : R.raw.fingerprint_edu_lottie_landscape_bottom_left; - case Surface.ROTATION_270 : - return isFolded ? R.raw.fingerprint_edu_lottie_folded_bottom_right - : R.raw.fingerprint_edu_lottie_portrait_bottom_right; - default : - return isFolded ? R.raw.fingerprint_edu_lottie_folded_top_right - : R.raw.fingerprint_edu_lottie_landscape_top_right; - } - } - - @Override - public void onAttach(@NonNull Context context) { - final FragmentActivity activity = getActivity(); - final ViewModelProvider provider = new ViewModelProvider(activity); - mViewModel = provider.get(FingerprintEnrollFindSensorViewModel.class); - mProgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class); - mRotationViewModel = provider.get(DeviceRotationViewModel.class); - mFoldedViewModel = provider.get(DeviceFoldedViewModel.class); - super.onAttach(context); - } -} diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.kt new file mode 100644 index 00000000000..16dfefaa921 --- /dev/null +++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.kt @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.biometrics2.ui.view + +import android.content.Context +import android.hardware.fingerprint.FingerprintManager +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.Surface +import android.view.View +import android.view.ViewGroup +import androidx.annotation.RawRes +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import com.airbnb.lottie.LottieAnimationView +import com.android.settings.R +import com.android.settings.biometrics2.ui.model.EnrollmentProgress +import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage +import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel +import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel +import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel +import com.android.settingslib.widget.LottieColorUtils +import com.google.android.setupcompat.template.FooterBarMixin +import com.google.android.setupcompat.template.FooterButton +import com.google.android.setupdesign.GlifLayout + +/** + * Fragment explaining the side fingerprint sensor location for fingerprint enrollment. + * It interacts with ProgressViewModel, FoldCallback (for different lottie), and + * LottieAnimationView. + *
+ * | Has                 | UDFPS | SFPS | Other (Rear FPS) |
+ * |---------------------|-------|------|------------------|
+ * | Primary button      | Yes   | No   | No               |
+ * | Illustration Lottie | Yes   | Yes  | No               |
+ * | Animation           | No    | No   | Depend on layout |
+ * | Progress ViewModel  | No    | Yes  | Yes              |
+ * | Orientation detect  | No    | Yes  | No               |
+ * | Foldable detect     | No    | Yes  | No               |
+ * 
+ */ +class FingerprintEnrollFindSfpsFragment : Fragment() { + + private var _viewModel: FingerprintEnrollFindSensorViewModel? = null + private val viewModel: FingerprintEnrollFindSensorViewModel + get() = _viewModel!! + + private var _progressViewModel: FingerprintEnrollProgressViewModel? = null + private val progressViewModel: FingerprintEnrollProgressViewModel + get() = _progressViewModel!! + + private var _rotationViewModel: DeviceRotationViewModel? = null + private val rotationViewModel: DeviceRotationViewModel + get() = _rotationViewModel!! + + private var _foldedViewModel: DeviceFoldedViewModel? = null + private val foldedViewModel: DeviceFoldedViewModel + get() = _foldedViewModel!! + + private var findSfpsView: GlifLayout? = null + + private val onSkipClickListener = + View.OnClickListener { _: View? -> viewModel.onSkipButtonClick() } + + private val illustrationLottie: LottieAnimationView + get() = findSfpsView!!.findViewById(R.id.illustration_lottie)!! + + @Surface.Rotation + private var animationRotation = -1 + + private val rotationObserver = Observer { rotation: Int? -> + if (DEBUG) { + Log.d(TAG, "rotationObserver $rotation") + } + rotation?.let { onRotationChanged(it) } + } + + private val progressObserver: Observer = + Observer { progress: EnrollmentProgress? -> + if (DEBUG) { + Log.d(TAG, "progressObserver($progress)") + } + if (progress != null && !progress.isInitialStep) { + stopLookingForFingerprint(true) + } + } + + private val lastCancelMessageObserver: Observer = + Observer { errorMessage: EnrollmentStatusMessage? -> + if (DEBUG) { + Log.d(TAG, "lastCancelMessageObserver($errorMessage)") + } + errorMessage?.let { onLastCancelMessage(it) } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View = (inflater.inflate( + R.layout.sfps_enroll_find_sensor_layout, + container, + false + ) as GlifLayout).also { + findSfpsView = it + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + requireActivity().bindFingerprintEnrollFindSfpsView( + view = findSfpsView!!, + onSkipClickListener = onSkipClickListener + ) + } + + override fun onStart() { + super.onStart() + val isEnrolling: Boolean = progressViewModel.isEnrolling + if (DEBUG) { + Log.d(TAG, "onStart(), isEnrolling:$isEnrolling") + } + if (!isEnrolling) { + startLookingForFingerprint() + } + } + + override fun onResume() { + super.onResume() + val rotationLiveData: LiveData = rotationViewModel.liveData + playLottieAnimation(rotationLiveData.value!!) + rotationLiveData.observe(this, rotationObserver) + } + + override fun onPause() { + rotationViewModel.liveData.removeObserver(rotationObserver) + super.onPause() + } + + override fun onStop() { + super.onStop() + val isEnrolling: Boolean = progressViewModel.isEnrolling + if (DEBUG) { + Log.d(TAG, "onStop(), isEnrolling:$isEnrolling") + } + if (isEnrolling) { + stopLookingForFingerprint(false) + } + } + + private fun startLookingForFingerprint() { + if (progressViewModel.isEnrolling) { + Log.d( + TAG, + "startLookingForFingerprint(), failed because isEnrolling is true before starting" + ) + return + } + progressViewModel.clearProgressLiveData() + progressViewModel.progressLiveData.observe(this, progressObserver) + val startResult: Boolean = + progressViewModel.startEnrollment(FingerprintManager.ENROLL_FIND_SENSOR) + if (!startResult) { + Log.e(TAG, "startLookingForFingerprint(), failed to start enrollment") + } + } + + private fun stopLookingForFingerprint(waitForLastCancelErrMsg: Boolean) { + if (!progressViewModel.isEnrolling) { + Log.d( + TAG, "stopLookingForFingerprint(), failed because isEnrolling is false before" + + " stopping" + ) + return + } + if (waitForLastCancelErrMsg) { + progressViewModel.clearErrorMessageLiveData() // Prevent got previous error message + progressViewModel.errorMessageLiveData.observe( + this, + lastCancelMessageObserver + ) + } + progressViewModel.progressLiveData.removeObserver(progressObserver) + val cancelResult: Boolean = progressViewModel.cancelEnrollment() + if (!cancelResult) { + Log.e(TAG, "stopLookingForFingerprint(), failed to cancel enrollment") + } + } + + private fun onRotationChanged(@Surface.Rotation newRotation: Int) { + if (DEBUG) { + Log.d(TAG, "onRotationChanged() from $animationRotation to $newRotation") + } + if ((newRotation + 2) % 4 == animationRotation) { + // Fragment not changed, we just need to play correct rotation animation + playLottieAnimation(newRotation) + } else if (newRotation % 2 != animationRotation % 2) { + // Fragment is going to be recreated, just stopLookingForFingerprint() here. + stopLookingForFingerprint(true) + } + } + + private fun onLastCancelMessage(errorMessage: EnrollmentStatusMessage) { + if (errorMessage.msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { + val progress: EnrollmentProgress? = progressViewModel.progressLiveData.value + progressViewModel.clearProgressLiveData() + progressViewModel.errorMessageLiveData.removeObserver(lastCancelMessageObserver) + if (progress != null && !progress.isInitialStep) { + viewModel.onStartButtonClick() + } + } else { + Log.e(TAG, "errorMessageObserver($errorMessage)") + } + } + + private fun playLottieAnimation(@Surface.Rotation rotation: Int) { + @RawRes val animationRawRes = getSfpsLottieAnimationRawRes(rotation) + if (DEBUG) { + Log.d( + TAG, + "play lottie animation $animationRawRes, previous rotation:$animationRotation" + + ", new rotation:" + rotation + ) + } + animationRotation = rotation + illustrationLottie.setAnimation(animationRawRes) + LottieColorUtils.applyDynamicColors(activity, illustrationLottie) + illustrationLottie.visibility = View.VISIBLE + illustrationLottie.playAnimation() + } + + @RawRes + private fun getSfpsLottieAnimationRawRes(@Surface.Rotation rotation: Int): Int { + val isFolded = java.lang.Boolean.FALSE != foldedViewModel.getLiveData().getValue() + return when (rotation) { + Surface.ROTATION_90 -> + if (isFolded) + R.raw.fingerprint_edu_lottie_folded_top_left + else + R.raw.fingerprint_edu_lottie_portrait_top_left + Surface.ROTATION_180 -> + if (isFolded) + R.raw.fingerprint_edu_lottie_folded_bottom_left + else + R.raw.fingerprint_edu_lottie_landscape_bottom_left + Surface.ROTATION_270 -> + if (isFolded) + R.raw.fingerprint_edu_lottie_folded_bottom_right + else + R.raw.fingerprint_edu_lottie_portrait_bottom_right + else -> + if (isFolded) + R.raw.fingerprint_edu_lottie_folded_top_right + else + R.raw.fingerprint_edu_lottie_landscape_top_right + } + } + + override fun onAttach(context: Context) { + ViewModelProvider(requireActivity()).let { provider -> + _viewModel = provider[FingerprintEnrollFindSensorViewModel::class.java] + _progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java] + _rotationViewModel = provider[DeviceRotationViewModel::class.java] + _foldedViewModel = provider[DeviceFoldedViewModel::class.java] + } + super.onAttach(context) + } + + companion object { + private const val DEBUG = false + private const val TAG = "FingerprintEnrollFindSfpsFragment" + } +} + +fun FragmentActivity.bindFingerprintEnrollFindSfpsView( + view: GlifLayout, + onSkipClickListener: View.OnClickListener +) { + view.getMixin(FooterBarMixin::class.java).let { + it.secondaryButton = FooterButton.Builder(this) + .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip) + .setButtonType(FooterButton.ButtonType.SKIP) + .setTheme(R.style.SudGlifButton_Secondary) + .build() + it.secondaryButton.setOnClickListener(onSkipClickListener) + } + + GlifLayoutHelper(this, view).let { + it.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title) + it.setDescriptionText( + getText(R.string.security_settings_sfps_enroll_find_sensor_message) + ) + } +}