() {};
+ new CreationExtras.Key<>() {};
@NonNull
@Override
@@ -59,7 +65,22 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
final BiometricsRepositoryProvider provider = FeatureFactory.getFactory(application)
.getBiometricsRepositoryProvider();
- if (modelClass.isAssignableFrom(FingerprintEnrollIntroViewModel.class)) {
+ if (modelClass.isAssignableFrom(AutoCredentialViewModel.class)) {
+ final LockPatternUtils lockPatternUtils =
+ featureFactory.getSecurityFeatureProvider().getLockPatternUtils(application);
+ final ChallengeGenerator challengeGenerator = extras.get(CHALLENGE_GENERATOR);
+ if (challengeGenerator != null) {
+ return (T) new AutoCredentialViewModel(application, lockPatternUtils,
+ challengeGenerator);
+ }
+ } else if (modelClass.isAssignableFrom(DeviceFoldedViewModel.class)) {
+ return (T) new DeviceFoldedViewModel(new ScreenSizeFoldProvider(application),
+ application.getMainExecutor());
+ } else if (modelClass.isAssignableFrom(DeviceRotationViewModel.class)) {
+ return (T) new DeviceRotationViewModel(application);
+ } else if (modelClass.isAssignableFrom(FingerprintEnrollFindSensorViewModel.class)) {
+ return (T) new FingerprintEnrollFindSensorViewModel(application);
+ } else if (modelClass.isAssignableFrom(FingerprintEnrollIntroViewModel.class)) {
final FingerprintRepository repository = provider.getFingerprintRepository(application);
if (repository != null) {
return (T) new FingerprintEnrollIntroViewModel(application, repository);
@@ -70,14 +91,9 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
return (T) new FingerprintEnrollmentViewModel(application, repository,
application.getSystemService(KeyguardManager.class));
}
- } else if (modelClass.isAssignableFrom(AutoCredentialViewModel.class)) {
- final LockPatternUtils lockPatternUtils =
- featureFactory.getSecurityFeatureProvider().getLockPatternUtils(application);
- final ChallengeGenerator challengeGenerator = extras.get(CHALLENGE_GENERATOR);
- if (challengeGenerator != null) {
- return (T) new AutoCredentialViewModel(application, lockPatternUtils,
- challengeGenerator);
- }
+ } else if (modelClass.isAssignableFrom(FingerprintEnrollProgressViewModel.class)) {
+ return (T) new FingerprintEnrollProgressViewModel(application,
+ new FingerprintUpdater(application));
}
return create(modelClass);
}
diff --git a/src/com/android/settings/biometrics2/ui/model/EnrollmentProgress.java b/src/com/android/settings/biometrics2/ui/model/EnrollmentProgress.java
new file mode 100644
index 00000000000..c62d670283a
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/model/EnrollmentProgress.java
@@ -0,0 +1,52 @@
+/*
+ * 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.model;
+
+/**
+ * Biometric Enrollment progress
+ */
+public final class EnrollmentProgress {
+
+ public static final int INITIAL_STEPS = -1;
+ public static final int INITIAL_REMAINING = 0;
+
+ private final int mSteps;
+ private final int mRemaining;
+
+ public EnrollmentProgress(int steps, int remaining) {
+ mSteps = steps;
+ mRemaining = remaining;
+ }
+
+ public int getSteps() {
+ return mSteps;
+ }
+
+ public int getRemaining() {
+ return mRemaining;
+ }
+
+ public boolean isInitialStep() {
+ return mSteps == INITIAL_STEPS;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode())
+ + "{steps:" + mSteps + ", remaining:" + mRemaining + "}";
+ }
+}
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.java
new file mode 100644
index 00000000000..d7537847f44
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.java
@@ -0,0 +1,209 @@
+/*
+ * 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 static android.view.View.OnClickListener;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+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 com.android.settings.R;
+import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation;
+import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
+import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
+import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
+
+import com.google.android.setupcompat.template.FooterBarMixin;
+import com.google.android.setupcompat.template.FooterButton;
+import com.google.android.setupdesign.GlifLayout;
+
+/**
+ * Fragment explaining the side fingerprint sensor location for fingerprint enrollment.
+ * It interacts with ProgressViewModel, and FingerprintFindSensorAnimation.
+ *
+ | 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 FingerprintEnrollFindRfpsFragment extends Fragment {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "FingerprintEnrollFindRfpsFragment";
+
+ private FingerprintEnrollFindSensorViewModel mViewModel;
+ private FingerprintEnrollProgressViewModel mPorgressViewModel;
+
+ private View mView;
+ private GlifLayout mGlifLayout;
+ private FooterBarMixin mFooterBarMixin;
+ private final OnClickListener mOnSkipClickListener = (v) -> mViewModel.onSkipButtonClick();
+ @Nullable private FingerprintFindSensorAnimation mAnimation;
+
+ private final Observer mProgressObserver = progress -> {
+ if (DEBUG) {
+ Log.d(TAG, "mProgressObserver(" + progress + ")");
+ }
+ if (progress != null && !progress.isInitialStep()) {
+ mViewModel.onStartButtonClick();
+ }
+ };
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ final Context context = inflater.getContext();
+ mView = inflater.inflate(R.layout.fingerprint_enroll_find_sensor, container, false);
+ mGlifLayout = mView.findViewById(R.id.setup_wizard_layout);
+ mFooterBarMixin = mGlifLayout.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()
+ );
+ View animationView = mView.findViewById(R.id.fingerprint_sensor_location_animation);
+ if (animationView instanceof FingerprintFindSensorAnimation) {
+ mAnimation = (FingerprintFindSensorAnimation) animationView;
+ }
+ 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, mGlifLayout);
+ glifLayoutHelper.setHeaderText(
+ R.string.security_settings_fingerprint_enroll_find_sensor_title);
+ glifLayoutHelper.setDescriptionText(
+ getText(R.string.security_settings_fingerprint_enroll_find_sensor_message));
+ mFooterBarMixin.getSecondaryButton().setOnClickListener(mOnSkipClickListener);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ if (DEBUG) {
+ Log.d(TAG, "onStart(), start looking for fingerprint, animation exist:"
+ + (mAnimation != null));
+ }
+ startLookingForFingerprint();
+ }
+
+ @Override
+ public void onResume() {
+ if (mAnimation != null) {
+ if (DEBUG) {
+ Log.d(TAG, "onResume(), start animation");
+ }
+ mAnimation.startAnimation();
+ }
+ super.onResume();
+ }
+
+ @Override
+ public void onPause() {
+ if (mAnimation != null) {
+ if (DEBUG) {
+ Log.d(TAG, "onPause(), pause animation");
+ }
+ mAnimation.pauseAnimation();
+ }
+ super.onPause();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (DEBUG) {
+ Log.d(TAG, "onStop(), stop looking for fingerprint, animation exist:"
+ + (mAnimation != null));
+ }
+ stopLookingForFingerprint();
+ }
+
+ private void startLookingForFingerprint() {
+ if (mPorgressViewModel.isEnrolling()) {
+ Log.d(TAG, "startLookingForFingerprint(), failed because isEnrolling is true before"
+ + " starting");
+ return;
+ }
+
+ mPorgressViewModel.clearProgressLiveData();
+ mPorgressViewModel.getProgressLiveData().observe(this, mProgressObserver);
+ final boolean startResult = mPorgressViewModel.startEnrollment(ENROLL_FIND_SENSOR);
+ if (!startResult) {
+ Log.e(TAG, "startLookingForFingerprint(), failed to start enrollment");
+ }
+ }
+
+ private void stopLookingForFingerprint() {
+ if (!mPorgressViewModel.isEnrolling()) {
+ Log.d(TAG, "stopLookingForFingerprint(), failed because isEnrolling is false before"
+ + " stopping");
+ return;
+ }
+
+ mPorgressViewModel.getProgressLiveData().removeObserver(mProgressObserver);
+ final boolean cancelResult = mPorgressViewModel.cancelEnrollment();
+ if (!cancelResult) {
+ Log.e(TAG, "stopLookingForFingerprint(), failed to cancel enrollment");
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mAnimation != null) {
+ if (DEBUG) {
+ Log.d(TAG, "onDestroy(), stop animation");
+ }
+ mAnimation.stopAnimation();
+ }
+ super.onDestroy();
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ final FragmentActivity activity = getActivity();
+ final ViewModelProvider provider = new ViewModelProvider(activity);
+ mViewModel = provider.get(FingerprintEnrollFindSensorViewModel.class);
+ mPorgressViewModel = provider.get(FingerprintEnrollProgressViewModel.class);
+ super.onAttach(context);
+ }
+}
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java
new file mode 100644
index 00000000000..c363f04e1e5
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.java
@@ -0,0 +1,251 @@
+/*
+ * 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.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.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 final Observer mRotationObserver = rotation -> {
+ if (DEBUG) {
+ Log.d(TAG, "rotationObserver " + rotation);
+ }
+ if (rotation == null) {
+ return;
+ }
+ onRotationChanged(rotation);
+ };
+
+ @Surface.Rotation private int mAnimationRotation = -1;
+
+ private View mView;
+ private GlifLayout mGlifLayout;
+ private FooterBarMixin mFooterBarMixin;
+ private final OnClickListener mOnSkipClickListener = (v) -> mViewModel.onSkipButtonClick();
+ private LottieAnimationView mIllustrationLottie;
+
+ private final Observer mProgressObserver = progress -> {
+ if (DEBUG) {
+ Log.d(TAG, "mProgressObserver(" + progress + ")");
+ }
+ if (progress != null && !progress.isInitialStep()) {
+ mViewModel.onStartButtonClick();
+ }
+ };
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ final Context context = inflater.getContext();
+ mView = inflater.inflate(R.layout.sfps_enroll_find_sensor_layout, container, false);
+ mGlifLayout = mView.findViewById(R.id.setup_wizard_layout);
+ mIllustrationLottie = mView.findViewById(R.id.illustration_lottie);
+ mFooterBarMixin = mGlifLayout.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, mGlifLayout);
+ 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();
+
+ if (DEBUG) {
+ Log.d(TAG, "onStart(), start looking for fingerprint");
+ }
+ 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();
+ if (DEBUG) {
+ Log.d(TAG, "onStop(), stop looking for fingerprint");
+ }
+ stopLookingForFingerprint();
+ }
+
+ 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() {
+ if (!mProgressViewModel.isEnrolling()) {
+ Log.d(TAG, "stopLookingForFingerprint(), failed because isEnrolling is false before"
+ + " stopping");
+ return;
+ }
+
+ 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) {
+ playLottieAnimation(newRotation);
+ }
+ // Fragment will be re-created if it's changed between landscape and portrait, so no need to
+ // handle other cases.
+ }
+
+ 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/FingerprintEnrollFindUdfpsFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindUdfpsFragment.java
new file mode 100644
index 00000000000..565f017b703
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindUdfpsFragment.java
@@ -0,0 +1,115 @@
+/*
+ * 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.view.View.OnClickListener;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.settings.R;
+import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
+
+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 under-display fingerprint sensor location for fingerprint enrollment.
+ * It interacts with Primary button, 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 FingerprintEnrollFindUdfpsFragment extends Fragment {
+
+ private FingerprintEnrollFindSensorViewModel mViewModel;
+
+ private View mView;
+ private GlifLayout mGlifLayout;
+ private FooterBarMixin mFooterBarMixin;
+ private final OnClickListener mOnSkipClickListener = (v) -> mViewModel.onSkipButtonClick();
+ private final OnClickListener mOnStartClickListener = (v) -> mViewModel.onStartButtonClick();
+ private LottieAnimationView mIllustrationLottie;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ final Context context = inflater.getContext();
+ mView = inflater.inflate(R.layout.udfps_enroll_find_sensor_layout, container, false);
+ mGlifLayout = mView.findViewById(R.id.setup_wizard_layout);
+ mIllustrationLottie = mView.findViewById(R.id.illustration_lottie);
+ mFooterBarMixin = mGlifLayout.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()
+ );
+ mFooterBarMixin.setPrimaryButton(
+ new FooterButton.Builder(context)
+ .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
+ .setButtonType(FooterButton.ButtonType.NEXT)
+ .setTheme(R.style.SudGlifButton_Primary)
+ .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, mGlifLayout);
+ glifLayoutHelper.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title);
+ glifLayoutHelper.setDescriptionText(
+ getText(R.string.security_settings_udfps_enroll_find_sensor_message));
+ mFooterBarMixin.getSecondaryButton().setOnClickListener(mOnSkipClickListener);
+ mFooterBarMixin.getPrimaryButton().setOnClickListener(mOnStartClickListener);
+ mIllustrationLottie.setOnClickListener(mOnStartClickListener);
+
+ if (mViewModel.isAccessibilityEnabled()) {
+ mIllustrationLottie.setAnimation(R.raw.udfps_edu_a11y_lottie);
+ }
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ mViewModel = new ViewModelProvider(getActivity()).get(
+ FingerprintEnrollFindSensorViewModel.class);
+ super.onAttach(context);
+ }
+}
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.java
index ecec36f50de..f6e06f9d938 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.java
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.java
@@ -24,18 +24,17 @@ import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroSt
import static com.google.android.setupdesign.util.DynamicColorPalette.ColorType.ACCENT;
-import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.os.Bundle;
import android.text.Html;
-import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
@@ -69,6 +68,9 @@ public class FingerprintEnrollIntroFragment extends Fragment {
private View mView = null;
private FooterButton mPrimaryFooterButton = null;
private FooterButton mSecondaryFooterButton = null;
+ private final OnClickListener mOnNextClickListener = (v) -> mViewModel.onNextButtonClick();
+ private final OnClickListener mOnSkipOrCancelClickListener =
+ (v) -> mViewModel.onSkipOrCancelButtonClick();
private ImageView mIconShield = null;
private TextView mFooterMessage6 = null;
@Nullable private PorterDuffColorFilter mIconColorFilter;
@@ -150,8 +152,8 @@ public class FingerprintEnrollIntroFragment extends Fragment {
final Context context = view.getContext();
- mPrimaryFooterButton.setOnClickListener(mViewModel::onNextButtonClick);
- mSecondaryFooterButton.setOnClickListener(mViewModel::onSkipOrCancelButtonClick);
+ mPrimaryFooterButton.setOnClickListener(mOnNextClickListener);
+ mSecondaryFooterButton.setOnClickListener(mOnSkipOrCancelClickListener);
if (mViewModel.canAssumeUdfps()) {
mFooterMessage6.setVisibility(View.VISIBLE);
@@ -165,15 +167,15 @@ public class FingerprintEnrollIntroFragment extends Fragment {
? R.string.security_settings_fingerprint_enroll_introduction_cancel
: R.string.security_settings_fingerprint_enroll_introduction_no_thanks);
+ final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(getActivity(), getLayout());
if (mViewModel.isBiometricUnlockDisabledByAdmin()
&& !mViewModel.isParentalConsentRequired()) {
- setHeaderText(
- getActivity(),
+ glifLayoutHelper.setHeaderText(
R.string.security_settings_fingerprint_enroll_introduction_title_unlock_disabled
);
- getLayout().setDescriptionText(getDescriptionDisabledByAdmin(context));
+ glifLayoutHelper.setDescriptionText(getDescriptionDisabledByAdmin(context));
} else {
- setHeaderText(getActivity(),
+ glifLayoutHelper.setHeaderText(
R.string.security_settings_fingerprint_enroll_introduction_title);
}
observePageStatusLiveDataIfNeed();
@@ -192,7 +194,7 @@ public class FingerprintEnrollIntroFragment extends Fragment {
final RequireScrollMixin requireScrollMixin = getLayout()
.getMixin(RequireScrollMixin.class);
requireScrollMixin.requireScrollWithButton(getActivity(), mPrimaryFooterButton,
- getMoreButtonTextRes(), mViewModel::onNextButtonClick);
+ getMoreButtonTextRes(), mOnNextClickListener);
// Always set true to setHasScrolledToBottom() before registering listener through
// setOnRequireScrollStateChangedListener(), because listener will not be called if first
@@ -253,21 +255,6 @@ public class FingerprintEnrollIntroFragment extends Fragment {
}
}
- private void setHeaderText(@NonNull Activity activity, int resId) {
- TextView layoutTitle = getLayout().getHeaderTextView();
- CharSequence previousTitle = layoutTitle.getText();
- CharSequence title = activity.getText(resId);
- if (previousTitle != title) {
- if (!TextUtils.isEmpty(previousTitle)) {
- layoutTitle.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
- }
- getLayout().setHeaderText(title);
- getLayout().getHeaderTextView().setContentDescription(title);
- activity.setTitle(title);
- }
- getLayout().getHeaderTextView().setContentDescription(activity.getText(resId));
- }
-
void updateFooterButtons(@NonNull FingerprintEnrollIntroStatus status) {
@StringRes final int scrollToBottomPrimaryResId =
status.getEnrollableStatus() == FINGERPRINT_ENROLLABLE_OK
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java
index cd4c539b361..366b7acef18 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.java
@@ -16,6 +16,7 @@
package com.android.settings.biometrics2.ui.view;
+import static androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY;
import static com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR;
@@ -23,24 +24,34 @@ import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewMo
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.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.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.Resources;
import android.graphics.Color;
import android.os.Bundle;
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;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.viewmodel.CreationExtras;
import androidx.lifecycle.viewmodel.MutableCreationExtras;
@@ -48,14 +59,16 @@ 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.biometrics.fingerprint.FingerprintEnrollFindSensor;
-import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor;
+import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
+import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollEnrolling;
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory;
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.FingerprintEnrollFindSensorViewModel;
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;
@@ -66,37 +79,57 @@ import com.google.android.setupdesign.util.ThemeHelper;
*/
public class FingerprintEnrollmentActivity extends FragmentActivity {
+ private static final boolean DEBUG = false;
private static final String TAG = "FingerprintEnrollmentActivity";
+ private static final String INTRO_TAG = "enroll-intro";
+ private static final String FIND_UDFPS_TAG = "enroll-find-udfps";
+ private static final String FIND_SFPS_TAG = "enroll-find-sfps";
+ private static final String FIND_RFPS_TAG = "enroll-find-rfps";
+ private static final String SKIP_SETUP_FIND_FPS_DIALOG_TAG = "skip-setup-dialog";
+
protected static final int LAUNCH_CONFIRM_LOCK_ACTIVITY = 1;
+ private ViewModelProvider mViewModelProvider;
private FingerprintEnrollmentViewModel mViewModel;
private AutoCredentialViewModel mAutoCredentialViewModel;
- private ActivityResultLauncher mNextActivityLauncher;
- private ActivityResultLauncher mChooseLockLauncher;
+ 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 ActivityResultCallback mNextActivityResultCallback =
+ result -> mViewModel.onContinueEnrollActivityResult(result,
+ mAutoCredentialViewModel.getUserId());
+ private final ActivityResultLauncher mNextActivityLauncher =
+ registerForActivityResult(new StartActivityForResult(), mNextActivityResultCallback);
+ 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);
- mNextActivityLauncher = registerForActivityResult(
- new ActivityResultContracts.StartActivityForResult(),
- (it) -> mViewModel.onContinueEnrollActivityResult(
- it,
- mAutoCredentialViewModel.getUserId())
- );
- mChooseLockLauncher = registerForActivityResult(
- new ActivityResultContracts.StartActivityForResult(),
- (it) -> onChooseOrConfirmLockResult(true, it)
- );
+ mViewModelProvider = new ViewModelProvider(this);
- ViewModelProvider viewModelProvider = new ViewModelProvider(this);
-
- mViewModel = viewModelProvider.get(FingerprintEnrollmentViewModel.class);
+ mViewModel = mViewModelProvider.get(FingerprintEnrollmentViewModel.class);
mViewModel.setRequest(new EnrollmentRequest(getIntent(), getApplicationContext()));
mViewModel.setSavedInstanceState(savedInstanceState);
- mAutoCredentialViewModel = viewModelProvider.get(AutoCredentialViewModel.class);
+ mAutoCredentialViewModel = mViewModelProvider.get(AutoCredentialViewModel.class);
mAutoCredentialViewModel.setCredentialModel(savedInstanceState, getIntent());
// Theme
@@ -106,20 +139,37 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
// fragment
setContentView(R.layout.biometric_enrollment_container);
- final FingerprintEnrollIntroViewModel introViewModel =
- viewModelProvider.get(FingerprintEnrollIntroViewModel.class);
- introViewModel.setEnrollmentRequest(mViewModel.getRequest());
- introViewModel.setUserId(mAutoCredentialViewModel.getUserId());
+ if (DEBUG) {
+ Log.e(TAG, "onCreate() has savedInstance:" + (savedInstanceState != null));
+ }
if (savedInstanceState == null) {
checkCredential();
-
- final String tag = "FingerprintEnrollIntroFragment";
- getSupportFragmentManager().beginTransaction()
- .setReorderingAllowed(true)
- .add(R.id.fragment_container_view, FingerprintEnrollIntroFragment.class, null,
- tag)
- .commit();
+ startIntroFragment();
+ } else {
+ final FragmentManager manager = getSupportFragmentManager();
+ String[] tags = new String[] {
+ FIND_UDFPS_TAG,
+ FIND_SFPS_TAG,
+ FIND_RFPS_TAG,
+ INTRO_TAG
+ };
+ for (String tag: tags) {
+ final Fragment fragment = manager.findFragmentByTag(tag);
+ if (fragment == null) {
+ continue;
+ }
+ if (DEBUG) {
+ Log.e(TAG, "onCreate() currentFragment:" + tag);
+ }
+ if (tag.equals(INTRO_TAG)) {
+ attachIntroViewModel();
+ } else { // FIND_UDFPS_TAG, FIND_SFPS_TAG, FIND_RFPS_TAG
+ attachFindSensorViewModel();
+ attachIntroViewModel();
+ }
+ break;
+ }
}
// observe LiveData
@@ -128,11 +178,80 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
mAutoCredentialViewModel.getGenerateChallengeFailedLiveData().observe(this,
this::onGenerateChallengeFailed);
+ }
+
+ private void startIntroFragment() {
+ attachIntroViewModel();
+ getSupportFragmentManager().beginTransaction()
+ .setReorderingAllowed(true)
+ .replace(R.id.fragment_container_view, FingerprintEnrollIntroFragment.class, null,
+ INTRO_TAG)
+ .commit();
+ }
+
+ private void attachIntroViewModel() {
+ final FingerprintEnrollIntroViewModel introViewModel =
+ mViewModelProvider.get(FingerprintEnrollIntroViewModel.class);
+
+ introViewModel.setEnrollmentRequest(mViewModel.getRequest());
+ introViewModel.setUserId(mAutoCredentialViewModel.getUserId());
// Clear ActionLiveData in FragmentViewModel to prevent getting previous action during
- // recreate, like press 'I agree' then press 'back' in FingerprintEnrollFindSensor activity.
+ // recreate, like press 'Agree' then press 'back' in FingerprintEnrollFindSensor activity.
introViewModel.clearActionLiveData();
- introViewModel.getActionLiveData().observe(this, this::observeIntroAction);
+ introViewModel.getActionLiveData().observe(this, mIntroActionObserver);
+ }
+
+ // We need to make sure token is valid before entering find sensor page
+ private void startFindSensorFragment() {
+ attachFindSensorViewModel();
+ if (mViewModel.canAssumeUdfps()) {
+ // UDFPS does not need to start real fingerprint enrolling during finding sensor
+ startFindFpsFragmentWithProgressViewModel(FingerprintEnrollFindUdfpsFragment.class,
+ FIND_UDFPS_TAG, false /* initProgressViewModel */);
+ } else if (mViewModel.canAssumeSfps()) {
+ startFindFpsFragmentWithProgressViewModel(FingerprintEnrollFindSfpsFragment.class,
+ FIND_SFPS_TAG, true /* initProgressViewModel */);
+ } else {
+ startFindFpsFragmentWithProgressViewModel(FingerprintEnrollFindRfpsFragment.class,
+ FIND_RFPS_TAG, true /* initProgressViewModel */);
+ }
+ }
+
+ private void startFindFpsFragmentWithProgressViewModel(
+ @NonNull Class extends Fragment> findFpsClass, @NonNull String tag,
+ boolean initProgressViewModel) {
+ if (initProgressViewModel) {
+ final FingerprintEnrollProgressViewModel progressViewModel =
+ mViewModelProvider.get(FingerprintEnrollProgressViewModel.class);
+ progressViewModel.setUserId(mAutoCredentialViewModel.getUserId());
+ progressViewModel.setToken(mAutoCredentialViewModel.getToken());
+ }
+ final FingerprintEnrollFindSensorViewModel findSensorViewModel =
+ mViewModelProvider.get(FingerprintEnrollFindSensorViewModel.class);
+ findSensorViewModel.setIsSuw(mViewModel.getRequest().isSuw());
+ getSupportFragmentManager().beginTransaction()
+ .setReorderingAllowed(true)
+ .setCustomAnimations(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out,
+ R.anim.sud_slide_back_in, R.anim.sud_slide_back_out)
+ .replace(R.id.fragment_container_view, findFpsClass, null, tag)
+ .addToBackStack(tag)
+ .commit();
+ }
+
+ private void attachFindSensorViewModel() {
+ 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 startSkipSetupFindFpsDialog() {
+ new SkipSetupFindFpsDialog().show(getSupportFragmentManager(),
+ SKIP_SETUP_FIND_FPS_DIALOG_TAG);
}
private void onGenerateChallengeFailed(@NonNull Boolean ignoredBoolean) {
@@ -217,10 +336,7 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
}
}
- private void observeIntroAction(@Nullable Integer action) {
- if (action == null) {
- return;
- }
+ private void onIntroAction(@FingerprintEnrollIntroAction int action) {
switch (action) {
case FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH: {
onSetActivityResult(
@@ -233,13 +349,30 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
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: {
+ startSkipSetupFindFpsDialog();
+ return;
+ }
+ case FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START: {
final boolean isSuw = mViewModel.getRequest().isSuw();
if (!mViewModel.isWaitingActivityResult().compareAndSet(false, true)) {
Log.w(TAG, "startNext, isSuw:" + isSuw + ", fail to set isWaiting flag");
}
- final Intent intent = new Intent(this, isSuw
- ? SetupFingerprintEnrollFindSensor.class
- : FingerprintEnrollFindSensor.class);
+ Intent intent = new Intent(this, isSuw
+ ? SetupFingerprintEnrollEnrolling.class
+ : FingerprintEnrollEnrolling.class);
intent.putExtras(mAutoCredentialViewModel.createCredentialIntentExtra());
intent.putExtras(mViewModel.getNextActivityBaseIntentExtras());
mNextActivityLauncher.launch(intent);
@@ -253,6 +386,12 @@ public class FingerprintEnrollmentActivity extends FragmentActivity {
mViewModel.checkFinishActivityDuringOnPause(isFinishing(), isChangingConfigurations());
}
+ @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) {
diff --git a/src/com/android/settings/biometrics2/ui/view/GlifLayoutHelper.java b/src/com/android/settings/biometrics2/ui/view/GlifLayoutHelper.java
new file mode 100644
index 00000000000..a1645d2f7ab
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/view/GlifLayoutHelper.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics2.ui.view;
+
+import android.app.Activity;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
+import com.google.android.setupdesign.GlifLayout;
+
+/**
+ * Utils class for GlifLayout
+ */
+public class GlifLayoutHelper {
+
+ @NonNull private final Activity mActivity;
+ @NonNull private final GlifLayout mGlifLayout;
+
+ public GlifLayoutHelper(@NonNull Activity activity, @NonNull GlifLayout glifLayout) {
+ mActivity = activity;
+ mGlifLayout = glifLayout;
+ }
+
+ /**
+ * Sets header text to GlifLayout
+ */
+ public void setHeaderText(@StringRes int textResId) {
+ TextView layoutTitle = mGlifLayout.getHeaderTextView();
+ CharSequence previousTitle = layoutTitle.getText();
+ CharSequence title = mActivity.getText(textResId);
+ if (previousTitle != title) {
+ if (!TextUtils.isEmpty(previousTitle)) {
+ layoutTitle.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
+ }
+ mGlifLayout.setHeaderText(title);
+ mGlifLayout.getHeaderTextView().setContentDescription(title);
+ mActivity.setTitle(title);
+ }
+ mGlifLayout.getHeaderTextView().setContentDescription(title);
+ }
+
+ /**
+ * Sets description text to GlifLayout
+ */
+ public void setDescriptionText(CharSequence description) {
+ CharSequence previousDescription = mGlifLayout.getDescriptionText();
+ // Prevent a11y for re-reading the same string
+ if (!TextUtils.equals(previousDescription, description)) {
+ mGlifLayout.setDescriptionText(description);
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics2/ui/view/SkipSetupFindFpsDialog.java b/src/com/android/settings/biometrics2/ui/view/SkipSetupFindFpsDialog.java
new file mode 100644
index 00000000000..b011267c1b2
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/view/SkipSetupFindFpsDialog.java
@@ -0,0 +1,71 @@
+/*
+ * 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.content.DialogInterface.OnClickListener;
+
+import android.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.settings.R;
+import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+
+/**
+ * Skip dialog which shows when user clicks "Do it later" button in FingerprintFindSensor page.
+ */
+public class SkipSetupFindFpsDialog extends InstrumentedDialogFragment {
+
+ private FingerprintEnrollFindSensorViewModel mViewModel;
+ private final OnClickListener mOnSkipClickListener =
+ (d, w) -> mViewModel.onSkipDialogButtonClick();
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.DIALOG_FINGERPRINT_SKIP_SETUP;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return onCreateDialogBuilder().create();
+ }
+
+ /**
+ * Returns builder for this dialog
+ */
+ @NonNull
+ private AlertDialog.Builder onCreateDialogBuilder() {
+ return new AlertDialog.Builder(getActivity(), R.style.Theme_AlertDialog)
+ .setTitle(R.string.setup_fingerprint_enroll_skip_title)
+ .setPositiveButton(R.string.skip_anyway_button_label, mOnSkipClickListener)
+ .setNegativeButton(R.string.go_back_button_label, null)
+ .setMessage(R.string.setup_fingerprint_enroll_skip_after_adding_lock_text);
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ mViewModel = new ViewModelProvider(getActivity()).get(
+ FingerprintEnrollFindSensorViewModel.class);
+ super.onAttach(context);
+ }
+}
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java
index a4f94b5771f..aa20f23dab9 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java
@@ -253,7 +253,11 @@ public class AutoCredentialViewModel extends AndroidViewModel {
if (isUnspecifiedPassword()) {
return CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK;
} else if (mCredentialModel.isValidGkPwHandle()) {
- generateChallenge(mCredentialModel.getGkPwHandle());
+ final long gkPwHandle = mCredentialModel.getGkPwHandle();
+ mCredentialModel.clearGkPwHandle();
+ // GkPwHandle is got through caller activity, we shall not revoke it after
+ // generateChallenge(). Let caller activity to make decision.
+ generateChallenge(gkPwHandle, false /* revokeGkPwHandle */);
mIsGeneratingChallengeDuringCheckingCredential = true;
return CREDENTIAL_IS_GENERATING_CHALLENGE;
} else {
@@ -261,7 +265,7 @@ public class AutoCredentialViewModel extends AndroidViewModel {
}
}
- private void generateChallenge(long gkPwHandle) {
+ private void generateChallenge(long gkPwHandle, boolean revokeGkPwHandle) {
mChallengeGenerator.setCallback((sensorId, userId, challenge) -> {
try {
final byte[] newToken = requestGatekeeperHat(gkPwHandle, challenge, userId);
@@ -274,11 +278,13 @@ public class AutoCredentialViewModel extends AndroidViewModel {
return;
}
- mLockPatternUtils.removeGatekeeperPasswordHandle(gkPwHandle);
- mCredentialModel.clearGkPwHandle();
+ if (revokeGkPwHandle) {
+ mLockPatternUtils.removeGatekeeperPasswordHandle(gkPwHandle);
+ }
if (DEBUG) {
- Log.d(TAG, "generateChallenge " + mCredentialModel);
+ Log.d(TAG, "generateChallenge(), model:" + mCredentialModel
+ + ", revokeGkPwHandle:" + revokeGkPwHandle);
}
// Check credential again
@@ -314,7 +320,9 @@ public class AutoCredentialViewModel extends AndroidViewModel {
if (data != null) {
final long gkPwHandle = result.getData().getLongExtra(
EXTRA_KEY_GK_PW_HANDLE, INVALID_GK_PW_HANDLE);
- generateChallenge(gkPwHandle);
+ // Revoke self requested GkPwHandle because it shall only used once inside this
+ // activity lifecycle.
+ generateChallenge(gkPwHandle, true /* revokeGkPwHandle */);
return true;
}
}
@@ -328,6 +336,14 @@ public class AutoCredentialViewModel extends AndroidViewModel {
return mCredentialModel.getUserId();
}
+ /**
+ * Get userId for this credential
+ */
+ @Nullable
+ public byte[] getToken() {
+ return mCredentialModel.getToken();
+ }
+
@Nullable
private byte[] requestGatekeeperHat(long gkPwHandle, long challenge, int userId)
throws IllegalStateException {
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/DeviceFoldedViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/DeviceFoldedViewModel.java
new file mode 100644
index 00000000000..6ea60b149a1
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/DeviceFoldedViewModel.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics2.ui.viewmodel;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
+import com.android.systemui.unfold.updates.FoldProvider;
+
+import java.util.concurrent.Executor;
+
+/**
+ * ViewModel explaining the fingerprint sensor location for fingerprint enrollment.
+ */
+public class DeviceFoldedViewModel extends ViewModel {
+
+ private static final String TAG = "DeviceFoldedViewModel";
+
+ @NonNull private final MutableLiveData mLiveData =
+ new MutableLiveData<>(null);
+
+ private final ScreenSizeFoldProvider mScreenSizeFoldProvider;
+ private final FoldProvider.FoldCallback mIsFoldedCallback = isFolded -> {
+ Log.d(TAG, "onFoldUpdated= " + isFolded);
+ mLiveData.postValue(isFolded);
+ };
+
+ public DeviceFoldedViewModel(@NonNull ScreenSizeFoldProvider screenSizeFoldProvider,
+ @NonNull Executor executor) {
+ super();
+ mScreenSizeFoldProvider = screenSizeFoldProvider;
+ mScreenSizeFoldProvider.registerCallback(mIsFoldedCallback, executor);
+ }
+
+ /**
+ * Returns FoldedLiveData
+ */
+ public LiveData getLiveData() {
+ return mLiveData;
+ }
+
+ @Override
+ protected void onCleared() {
+ mScreenSizeFoldProvider.unregisterCallback(mIsFoldedCallback);
+ super.onCleared();
+ }
+}
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/DeviceRotationViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/DeviceRotationViewModel.java
new file mode 100644
index 00000000000..c9135fb6f0b
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/DeviceRotationViewModel.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics2.ui.viewmodel;
+
+import static android.hardware.display.DisplayManager.DisplayListener;
+
+import android.app.Application;
+import android.hardware.display.DisplayManager;
+import android.util.Log;
+import android.view.DisplayInfo;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+/**
+ * ViewModel explaining the fingerprint sensor location for fingerprint enrollment.
+ */
+public class DeviceRotationViewModel extends AndroidViewModel {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "RotationViewModel";
+
+ private final DisplayManager mDisplayManager;
+ @NonNull private final DisplayInfo mDisplayInfo = new DisplayInfo();
+ private final DisplayListener mDisplayListener = new DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ final int rotation = getRotation();
+ if (DEBUG) {
+ Log.d(TAG, "onDisplayChanged(" + displayId + "), rotation:" + rotation);
+ }
+ mLiveData.postValue(rotation);
+ }
+ };
+
+ @NonNull private final MutableLiveData mLiveData =
+ new MutableLiveData<>(getRotation());
+
+ public DeviceRotationViewModel(@NonNull Application application) {
+ super(application);
+ mDisplayManager = application.getSystemService(DisplayManager.class);
+ mDisplayManager.registerDisplayListener(mDisplayListener,
+ application.getMainThreadHandler());
+ }
+
+ /**
+ * Returns current rotation
+ */
+ @Surface.Rotation
+ private int getRotation() {
+ getApplication().getDisplay().getDisplayInfo(mDisplayInfo);
+ return mDisplayInfo.rotation;
+ }
+
+ /**
+ * Returns RotationLiveData
+ */
+ public LiveData getLiveData() {
+ return mLiveData;
+ }
+
+ @Override
+ protected void onCleared() {
+ mDisplayManager.unregisterDisplayListener(mDisplayListener);
+ super.onCleared();
+ }
+}
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollFindSensorViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollFindSensorViewModel.java
new file mode 100644
index 00000000000..a871fe120a8
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollFindSensorViewModel.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics2.ui.viewmodel;
+
+import android.annotation.IntDef;
+import android.app.Application;
+import android.util.Log;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * ViewModel explaining the fingerprint sensor location for fingerprint enrollment.
+ */
+public class FingerprintEnrollFindSensorViewModel extends AndroidViewModel {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "FingerprintEnrollFindSensorViewModel";
+
+ /**
+ * User clicks 'Skip' button on this page in Settings
+ */
+ public static final int FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP = 0;
+
+ /**
+ * User clicks 'Skip' button on this page in SetupWizard flow
+ */
+ public static final int FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG = 1;
+
+ /**
+ * User clicks 'Start' button on this page
+ */
+ public static final int FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START = 2;
+
+ @IntDef(prefix = { "FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_" }, value = {
+ FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP,
+ FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG,
+ FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FingerprintEnrollFindSensorAction {}
+
+ private final AccessibilityManager mAccessibilityManager;
+
+ private boolean mIsSuw = false;
+ @NonNull private final MutableLiveData mActionLiveData = new MutableLiveData<>();
+
+ public FingerprintEnrollFindSensorViewModel(@NonNull Application application) {
+ super(application);
+ mAccessibilityManager = application.getSystemService(AccessibilityManager.class);
+ }
+
+ /**
+ * Sets isSetupWizard or not
+ */
+ public void setIsSuw(boolean isSuw) {
+ mIsSuw = isSuw;
+ }
+
+ /**
+ * Returns action live data that user chooses
+ */
+ public LiveData getActionLiveData() {
+ return mActionLiveData;
+ }
+
+ /**
+ * Clear ActionLiveData to prevent get obsolete data
+ */
+ public void clearActionLiveData() {
+ mActionLiveData.setValue(null);
+ }
+
+ /**
+ * User clicks skip button on dialog
+ */
+ public void onSkipDialogButtonClick() {
+ final int action = FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP;
+ if (DEBUG) {
+ Log.d(TAG, "onSkipDialogButtonClick, post " + action);
+ }
+ mActionLiveData.postValue(action);
+ }
+
+ /**
+ * User clicks skip button
+ */
+ public void onSkipButtonClick() {
+ final int action = mIsSuw
+ ? FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG
+ : FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP;
+ if (DEBUG) {
+ Log.d(TAG, "onSkipButtonClick, post action " + action);
+ }
+ mActionLiveData.postValue(action);
+ }
+
+ /**
+ * User clicks start button
+ */
+ public void onStartButtonClick() {
+ final int action = FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_START;
+ if (DEBUG) {
+ Log.d(TAG, "onStartButtonClick, post action " + action);
+ }
+ mActionLiveData.postValue(action);
+ }
+
+ /**
+ * Returns the info about accessibility is enabled or not
+ */
+ public boolean isAccessibilityEnabled() {
+ return mAccessibilityManager.isEnabled();
+ }
+}
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java
index 72611a7c791..228ea19a8d4 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java
@@ -24,7 +24,6 @@ import android.annotation.IntDef;
import android.app.Application;
import android.os.UserHandle;
import android.util.Log;
-import android.view.View;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
@@ -189,7 +188,7 @@ public class FingerprintEnrollIntroViewModel extends AndroidViewModel
/**
* User clicks next button
*/
- public void onNextButtonClick(View ignoredView) {
+ public void onNextButtonClick() {
final Integer status = mEnrollableStatusLiveData.getValue();
switch (status != null ? status : ENROLLABLE_STATUS_DEFAULT) {
case FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX:
@@ -206,7 +205,7 @@ public class FingerprintEnrollIntroViewModel extends AndroidViewModel
/**
* User clicks skip/cancel button
*/
- public void onSkipOrCancelButtonClick(View ignoredView) {
+ public void onSkipOrCancelButtonClick() {
mActionLiveData.postValue(FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL);
}
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
new file mode 100644
index 00000000000..cec94751f73
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics2.ui.viewmodel;
+
+import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_REMAINING;
+import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_STEPS;
+
+import android.app.Application;
+import android.content.res.Resources;
+import android.hardware.fingerprint.FingerprintManager.EnrollReason;
+import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
+import android.os.CancellationSignal;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.android.settings.R;
+import com.android.settings.biometrics.fingerprint.FingerprintUpdater;
+import com.android.settings.biometrics.fingerprint.MessageDisplayController;
+import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
+
+/**
+ * Progress ViewModel handles the state around biometric enrollment. It manages the state of
+ * enrollment throughout the activity lifecycle so the app can continue after an event like
+ * rotation.
+ */
+public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "FingerprintEnrollProgressViewModel";
+
+ private final MutableLiveData mProgressLiveData = new MutableLiveData<>(
+ new EnrollmentProgress(INITIAL_STEPS, INITIAL_REMAINING));
+
+ private byte[] mToken = null;
+ private int mUserId = UserHandle.myUserId();
+
+ private final FingerprintUpdater mFingerprintUpdater;
+ private final MessageDisplayController mMessageDisplayController;
+ private EnrollmentHelper mEnrollmentHelper;
+ private final EnrollmentCallback mEnrollmentCallback = new EnrollmentCallback() {
+
+ @Override
+ public void onEnrollmentProgress(int remaining) {
+ final int currentSteps = getSteps();
+ final EnrollmentProgress progress = new EnrollmentProgress(
+ currentSteps == INITIAL_STEPS ? remaining : getSteps(), remaining);
+ if (DEBUG) {
+ Log.d(TAG, "onEnrollmentProgress(" + remaining + "), steps: " + currentSteps
+ + ", post progress as " + progress);
+ }
+ mProgressLiveData.postValue(progress);
+ // TODO set enrolling to false when remaining is 0 during implementing b/260957933
+ }
+
+ @Override
+ public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
+ // TODO add LiveData for help message during implementing b/260957933
+ }
+
+ @Override
+ public void onEnrollmentError(int errMsgId, CharSequence errString) {
+ // TODO add LiveData for error message during implementing b/260957933
+ }
+ };
+
+ public FingerprintEnrollProgressViewModel(@NonNull Application application,
+ @NonNull FingerprintUpdater fingerprintUpdater) {
+ super(application);
+ mFingerprintUpdater = fingerprintUpdater;
+ final Resources res = application.getResources();
+ mMessageDisplayController =
+ res.getBoolean(R.bool.enrollment_message_display_controller_flag)
+ ? new MessageDisplayController(
+ application.getMainThreadHandler(),
+ mEnrollmentCallback,
+ SystemClock.elapsedRealtimeClock(),
+ res.getInteger(R.integer.enrollment_help_minimum_time_display),
+ res.getInteger(R.integer.enrollment_progress_minimum_time_display),
+ res.getBoolean(R.bool.enrollment_progress_priority_over_help),
+ res.getBoolean(R.bool.enrollment_prioritize_acquire_messages),
+ res.getInteger(R.integer.enrollment_collect_time)) : null;
+ }
+
+ public void setToken(byte[] token) {
+ mToken = token;
+ }
+
+ public void setUserId(int userId) {
+ mUserId = userId;
+ }
+
+ /**
+ * clear progress
+ */
+ public void clearProgressLiveData() {
+ mProgressLiveData.setValue(new EnrollmentProgress(INITIAL_STEPS, INITIAL_REMAINING));
+ }
+
+ public LiveData getProgressLiveData() {
+ return mProgressLiveData;
+ }
+
+ /**
+ * Starts enrollment and return latest isEnrolling() result
+ */
+ public boolean startEnrollment(@EnrollReason int reason) {
+ if (mToken == null) {
+ Log.e(TAG, "Null hardware auth token for enroll");
+ return false;
+ }
+ if (isEnrolling()) {
+ Log.w(TAG, "Enrolling has started, shall not start again");
+ return true;
+ }
+
+ mEnrollmentHelper = new EnrollmentHelper(
+ mMessageDisplayController != null
+ ? mMessageDisplayController
+ : mEnrollmentCallback);
+ mEnrollmentHelper.startEnrollment(mFingerprintUpdater, mToken, mUserId, reason);
+ return true;
+ }
+
+ /**
+ * Cancels enrollment and return latest isEnrolling result
+ */
+ public boolean cancelEnrollment() {
+ if (!isEnrolling() || mEnrollmentHelper == null) {
+ Log.e(TAG, "Fail to cancel enrollment, enrollmentController exist:"
+ + (mEnrollmentHelper != null));
+ return false;
+ }
+
+ mEnrollmentHelper.cancelEnrollment();
+ mEnrollmentHelper = null;
+ return true;
+ }
+
+ public boolean isEnrolling() {
+ return (mEnrollmentHelper != null);
+ }
+
+ private int getSteps() {
+ return mProgressLiveData.getValue().getSteps();
+ }
+
+ /**
+ * This class is used to stop latest message from onEnrollmentError() after user cancelled
+ * enrollment. This class will not forward message anymore after mCancellationSignal is sent.
+ */
+ private static class EnrollmentHelper extends EnrollmentCallback {
+
+ @NonNull private final EnrollmentCallback mEnrollmentCallback;
+ @Nullable private CancellationSignal mCancellationSignal = new CancellationSignal();
+
+ EnrollmentHelper(@NonNull EnrollmentCallback enrollmentCallback) {
+ mEnrollmentCallback = enrollmentCallback;
+ }
+
+ @Override
+ public void onEnrollmentError(int errMsgId, CharSequence errString) {
+ if (mCancellationSignal == null) {
+ return;
+ }
+ mEnrollmentCallback.onEnrollmentError(errMsgId, errString);
+ }
+
+ @Override
+ public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
+ if (mCancellationSignal == null) {
+ return;
+ }
+ mEnrollmentCallback.onEnrollmentHelp(helpMsgId, helpString);
+ }
+
+ @Override
+ public void onEnrollmentProgress(int remaining) {
+ if (mCancellationSignal == null) {
+ return;
+ }
+ mEnrollmentCallback.onEnrollmentProgress(remaining);
+ }
+
+ /**
+ * Starts enrollment
+ */
+ public boolean startEnrollment(@NonNull FingerprintUpdater fingerprintUpdater,
+ @NonNull byte[] token, int userId, @EnrollReason int reason) {
+ if (mCancellationSignal == null) {
+ // Not allow enrolling twice as same instance. Allocate a new instance for second
+ // enrollment.
+ return false;
+ }
+ fingerprintUpdater.enroll(token, mCancellationSignal, userId, this, reason);
+ return true;
+ }
+
+ /**
+ * Cancels current enrollment
+ */
+ public void cancelEnrollment() {
+ final CancellationSignal cancellationSignal = mCancellationSignal;
+ mCancellationSignal = null;
+
+ if (cancellationSignal != null) {
+ cancellationSignal.cancel();
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModel.java
index 4b862731da5..30ed2032edd 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModel.java
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModel.java
@@ -124,8 +124,6 @@ public class FingerprintEnrollmentViewModel extends AndroidViewModel implements
}
}
-
-
private boolean isKeyguardSecure() {
return mKeyguardManager != null && mKeyguardManager.isKeyguardSecure();
}
@@ -182,4 +180,18 @@ public class FingerprintEnrollmentViewModel extends AndroidViewModel implements
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBoolean(SAVED_STATE_IS_WAITING_ACTIVITY_RESULT, mIsWaitingActivityResult.get());
}
+
+ /**
+ * The first sensor type is UDFPS sensor or not
+ */
+ public boolean canAssumeUdfps() {
+ return mFingerprintRepository.canAssumeUdfps();
+ }
+
+ /**
+ * The first sensor type is side fps sensor or not
+ */
+ public boolean canAssumeSfps() {
+ return mFingerprintRepository.canAssumeSfps();
+ }
}
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
index 585a5f9a2d0..6425814a9e3 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
@@ -19,6 +19,7 @@ package com.android.settings.bluetooth;
import static com.android.settings.bluetooth.BluetoothDeviceDetailsFragment.FEATURE_HEARING_DEVICE_CONTROLS_ORDER;
import android.content.Context;
+import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import androidx.preference.Preference;
@@ -27,16 +28,22 @@ import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityHearingAidsFragment;
+import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.google.common.annotations.VisibleForTesting;
+
/**
* The controller of the hearing device controls in the bluetooth detail settings.
*/
-public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDetailsController {
+public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDetailsController
+ implements Preference.OnPreferenceClickListener {
private static final String KEY_FEATURE_CONTROLS_GROUP = "feature_controls_group";
- private static final String KEY_HEARING_DEVICE_CONTROLS = "hearing_device_controls";
+ @VisibleForTesting
+ static final String KEY_HEARING_DEVICE_CONTROLS = "hearing_device_controls";
public BluetoothDetailsHearingDeviceControlsController(Context context,
PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) {
@@ -70,12 +77,30 @@ public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDe
return KEY_FEATURE_CONTROLS_GROUP;
}
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (TextUtils.equals(preference.getKey(), KEY_HEARING_DEVICE_CONTROLS)) {
+ launchAccessibilityHearingDeviceSettings();
+ return true;
+ }
+ return false;
+ }
+
private Preference createHearingDeviceControlsPreference(Context context) {
final Preference preference = new Preference(context);
preference.setKey(KEY_HEARING_DEVICE_CONTROLS);
preference.setTitle(context.getString(R.string.bluetooth_device_controls_title));
preference.setSummary(context.getString(R.string.bluetooth_device_controls_summary));
+ preference.setOnPreferenceClickListener(this);
return preference;
}
+
+ private void launchAccessibilityHearingDeviceSettings() {
+ new SubSettingLauncher(mContext)
+ .setDestination(AccessibilityHearingAidsFragment.class.getName())
+ .setSourceMetricsCategory(
+ ((BluetoothDeviceDetailsFragment) mFragment).getMetricsCategory())
+ .launch();
+ }
}
diff --git a/src/com/android/settings/communal/CommunalPreferenceController.java b/src/com/android/settings/communal/CommunalPreferenceController.java
index e16dcc3435a..5cfedc45de6 100644
--- a/src/com/android/settings/communal/CommunalPreferenceController.java
+++ b/src/com/android/settings/communal/CommunalPreferenceController.java
@@ -17,6 +17,7 @@
package com.android.settings.communal;
import android.content.Context;
+import android.os.UserManager;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
@@ -31,7 +32,14 @@ public class CommunalPreferenceController extends BasePreferenceController {
@Override
public int getAvailabilityStatus() {
- return mContext.getResources().getBoolean(R.bool.config_show_communal_settings)
+ // TODO(b/257333623): Allow the communal user to be non-SystemUser user in HSUM.
+ return (mContext.getResources().getBoolean(R.bool.config_show_communal_settings)
+ && isSystemUser())
? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
+
+ private boolean isSystemUser() {
+ final UserManager userManager = mContext.getSystemService(UserManager.class);
+ return userManager != null && userManager.isSystemUser();
+ }
}
diff --git a/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java b/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java
index 7ed02491ca3..e04251a218e 100644
--- a/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java
+++ b/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java
@@ -85,16 +85,24 @@ public class StylusDevicesController extends AbstractPreferenceController implem
}
@Nullable
- private Preference createDefaultNotesPreference() {
+ private Preference createOrUpdateDefaultNotesPreference(@Nullable Preference preference) {
RoleManager rm = mContext.getSystemService(RoleManager.class);
- if (rm == null) {
+ if (rm == null || !rm.isRoleAvailable(RoleManager.ROLE_NOTES)) {
return null;
}
+ Preference pref = preference == null ? new Preference(mContext) : preference;
+ pref.setKey(KEY_DEFAULT_NOTES);
+ pref.setTitle(mContext.getString(R.string.stylus_default_notes_app));
+ pref.setIcon(R.drawable.ic_article);
+ pref.setOnPreferenceClickListener(this);
+ pref.setEnabled(true);
+
List roleHolders = rm.getRoleHoldersAsUser(RoleManager.ROLE_NOTES,
mContext.getUser());
if (roleHolders.isEmpty()) {
- return null;
+ pref.setSummary(R.string.default_app_none);
+ return pref;
}
String packageName = roleHolders.get(0);
@@ -107,13 +115,6 @@ public class StylusDevicesController extends AbstractPreferenceController implem
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Notes role package not found.");
}
-
- Preference pref = new Preference(mContext);
- pref.setKey(KEY_DEFAULT_NOTES);
- pref.setTitle(mContext.getString(R.string.stylus_default_notes_app));
- pref.setIcon(R.drawable.ic_article);
- pref.setOnPreferenceClickListener(this);
- pref.setEnabled(true);
pref.setSummary(appName);
return pref;
}
@@ -187,12 +188,10 @@ public class StylusDevicesController extends AbstractPreferenceController implem
private void refresh() {
if (!isAvailable()) return;
- Preference notesPref = mPreferencesContainer.findPreference(KEY_DEFAULT_NOTES);
- if (notesPref == null) {
- notesPref = createDefaultNotesPreference();
- if (notesPref != null) {
- mPreferencesContainer.addPreference(notesPref);
- }
+ Preference currNotesPref = mPreferencesContainer.findPreference(KEY_DEFAULT_NOTES);
+ Preference notesPref = createOrUpdateDefaultNotesPreference(currNotesPref);
+ if (currNotesPref == null && notesPref != null) {
+ mPreferencesContainer.addPreference(notesPref);
}
Preference handwritingPref = mPreferencesContainer.findPreference(KEY_HANDWRITING);
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index 2243f5764e5..5d80193a208 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -164,6 +164,7 @@ import com.android.settings.print.PrintJobSettingsFragment;
import com.android.settings.print.PrintSettingsFragment;
import com.android.settings.privacy.PrivacyControlsFragment;
import com.android.settings.privacy.PrivacyDashboardFragment;
+import com.android.settings.regionalpreferences.RegionalPreferencesEntriesFragment;
import com.android.settings.security.LockscreenDashboardFragment;
import com.android.settings.security.MemtagPage;
import com.android.settings.security.SecurityAdvancedSettings;
@@ -367,7 +368,8 @@ public class SettingsGateway {
TurnScreenOnDetails.class.getName(),
NfcAndPaymentFragment.class.getName(),
ColorAndMotionFragment.class.getName(),
- LongBackgroundTasksDetails.class.getName()
+ LongBackgroundTasksDetails.class.getName(),
+ RegionalPreferencesEntriesFragment.class.getName()
};
public static final String[] SETTINGS_FOR_RESTRICTED = {
diff --git a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java
index 991ad25a457..5e5e2abce82 100644
--- a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java
+++ b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java
@@ -55,6 +55,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.function.Consumer;
@SearchIndexable
public class MyDeviceInfoFragment extends DashboardFragment
@@ -79,7 +80,6 @@ public class MyDeviceInfoFragment extends DashboardFragment
@Override
public void onAttach(Context context) {
super.onAttach(context);
- use(ImeiInfoPreferenceController.class).setHost(this /* parent */);
use(DeviceNamePreferenceController.class).setHost(this /* parent */);
mBuildNumberPreferenceController = use(BuildNumberPreferenceController.class);
mBuildNumberPreferenceController.setHost(this /* parent */);
@@ -126,12 +126,27 @@ public class MyDeviceInfoFragment extends DashboardFragment
controllers.add(new FccEquipmentIdPreferenceController(context));
controllers.add(new UptimePreferenceController(context, lifecycle));
+ Consumer imeiInfoList = imeiKey -> {
+ ImeiInfoPreferenceController imeiRecord =
+ new ImeiInfoPreferenceController(context, imeiKey);
+ imeiRecord.init(fragment, slotSimStatus);
+ controllers.add(imeiRecord);
+ };
+
+ if (fragment != null) {
+ imeiInfoList.accept(ImeiInfoPreferenceController.DEFAULT_KEY);
+ }
+
for (int slotIndex = 0; slotIndex < slotSimStatus.size(); slotIndex ++) {
SimStatusPreferenceController slotRecord =
new SimStatusPreferenceController(context,
slotSimStatus.getPreferenceKey(slotIndex));
slotRecord.init(fragment, slotSimStatus);
controllers.add(slotRecord);
+
+ if (fragment != null) {
+ imeiInfoList.accept(ImeiInfoPreferenceController.DEFAULT_KEY + (1 + slotIndex));
+ }
}
EidStatus eidStatus = new EidStatus(slotSimStatus, context, executor);
diff --git a/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java b/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java
index a5019f896cf..13c5989a1dd 100644
--- a/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java
@@ -23,6 +23,7 @@ import android.os.UserManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
@@ -33,70 +34,80 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.deviceinfo.PhoneNumberSummaryPreference;
+import com.android.settings.deviceinfo.simstatus.SlotSimStatus;
import com.android.settings.network.SubscriptionUtil;
import com.android.settingslib.Utils;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* Controller that manages preference for single and multi sim devices.
*/
public class ImeiInfoPreferenceController extends BasePreferenceController {
- private static final String KEY_PREFERENCE_CATEGORY = "device_detail_category";
+ private static final String TAG = "ImeiInfoPreferenceController";
- private final boolean mIsMultiSim;
- private final TelephonyManager mTelephonyManager;
- private final List mPreferenceList = new ArrayList<>();
+ private static final String KEY_PREFERENCE_CATEGORY = "device_detail_category";
+ public static final String DEFAULT_KEY = "imei_info";
+
+ private TelephonyManager mTelephonyManager;
private Fragment mFragment;
+ private SlotSimStatus mSlotSimStatus;
public ImeiInfoPreferenceController(Context context, String key) {
super(context, key);
- mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- mIsMultiSim = mTelephonyManager.getPhoneCount() > 1;
}
- public void setHost(Fragment fragment) {
+ public void init(Fragment fragment, SlotSimStatus slotSimStatus) {
mFragment = fragment;
+ mSlotSimStatus = slotSimStatus;
+ }
+
+ private boolean isMultiSim() {
+ return (mSlotSimStatus != null) && (mSlotSimStatus.size() > 1);
+ }
+
+ private int keyToSlotIndex(String key) {
+ int simSlot = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+ try {
+ simSlot = Integer.valueOf(key.replace(DEFAULT_KEY, "")) - 1;
+ } catch (Exception exception) {
+ Log.i(TAG, "Invalid key : " + key);
+ }
+ return simSlot;
+ }
+
+ private SubscriptionInfo getSubscriptionInfo(int simSlot) {
+ return (mSlotSimStatus == null) ? null : mSlotSimStatus.getSubscriptionInfo(simSlot);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
- if (!SubscriptionUtil.isSimHardwareVisible(mContext)) {
+ if ((!SubscriptionUtil.isSimHardwareVisible(mContext)) || (mSlotSimStatus == null)) {
return;
}
- final Preference preference = screen.findPreference(getPreferenceKey());
- final PreferenceCategory category = screen.findPreference(KEY_PREFERENCE_CATEGORY);
-
- mPreferenceList.add(preference);
- updatePreference(preference, 0 /* simSlot */);
-
- final int imeiPreferenceOrder = preference.getOrder();
- // Add additional preferences for each sim in the device
- for (int simSlotNumber = 1; simSlotNumber < mTelephonyManager.getPhoneCount();
- simSlotNumber++) {
- final Preference multiSimPreference = createNewPreference(screen.getContext());
- multiSimPreference.setCopyingEnabled(true);
- multiSimPreference.setOrder(imeiPreferenceOrder + simSlotNumber);
- multiSimPreference.setKey(getPreferenceKey() + simSlotNumber);
- category.addPreference(multiSimPreference);
- mPreferenceList.add(multiSimPreference);
- updatePreference(multiSimPreference, simSlotNumber);
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ Preference preference = screen.findPreference(DEFAULT_KEY);
+ if (!isAvailable() || preference == null || !preference.isVisible()) {
+ return;
}
+ PreferenceCategory category = screen.findPreference(KEY_PREFERENCE_CATEGORY);
+
+ int imeiPreferenceOrder = preference.getOrder();
+ screen.removePreference(preference);
+ preference.setVisible(false);
+
+ // Add additional preferences for each imei slot in the device
+ for (int simSlotNumber = 0; simSlotNumber < mSlotSimStatus.size(); simSlotNumber++) {
+ Preference multiImeiPreference = createNewPreference(screen.getContext());
+ multiImeiPreference.setOrder(imeiPreferenceOrder + 1 + simSlotNumber);
+ multiImeiPreference.setKey(DEFAULT_KEY + (1 + simSlotNumber));
+ category.addPreference(multiImeiPreference);
+ }
}
@Override
public void updateState(Preference preference) {
- if (preference == null) {
- return;
- }
- int size = mPreferenceList.size();
- for (int i = 0; i < size; i++) {
- Preference pref = mPreferenceList.get(i);
- updatePreference(pref, i);
- }
+ updatePreference(preference, keyToSlotIndex(preference.getKey()));
}
@Override
@@ -112,8 +123,8 @@ public class ImeiInfoPreferenceController extends BasePreferenceController {
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
- final int simSlot = mPreferenceList.indexOf(preference);
- if (simSlot == -1) {
+ final int simSlot = keyToSlotIndex(preference.getKey());
+ if (simSlot < 0) {
return false;
}
@@ -124,9 +135,10 @@ public class ImeiInfoPreferenceController extends BasePreferenceController {
@Override
public int getAvailabilityStatus() {
- return SubscriptionUtil.isSimHardwareVisible(mContext) &&
- mContext.getSystemService(UserManager.class).isAdminUser()
- && !Utils.isWifiOnly(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ boolean isAvailable = SubscriptionUtil.isSimHardwareVisible(mContext) &&
+ mContext.getSystemService(UserManager.class).isAdminUser() &&
+ !Utils.isWifiOnly(mContext);
+ return isAvailable ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
@@ -135,29 +147,48 @@ public class ImeiInfoPreferenceController extends BasePreferenceController {
}
private void updatePreference(Preference preference, int simSlot) {
+ SubscriptionInfo subInfo = getSubscriptionInfo(simSlot);
+ preference.setEnabled(subInfo != null);
+ preference.setCopyingEnabled(subInfo != null);
preference.setTitle(getTitle(simSlot));
preference.setSummary(getSummary());
}
- private CharSequence getTitleForGsmPhone(int simSlot) {
- return mIsMultiSim ? mContext.getString(R.string.imei_multi_sim, simSlot + 1)
+ private CharSequence getTitleForGsmPhone(int simSlot, boolean isPrimaryImei) {
+ int titleId = isPrimaryImei ? R.string.imei_multi_sim_primary : R.string.imei_multi_sim;
+ return isMultiSim() ? mContext.getString(titleId, simSlot + 1)
: mContext.getString(R.string.status_imei);
}
- private CharSequence getTitleForCdmaPhone(int simSlot) {
- return mIsMultiSim ? mContext.getString(R.string.meid_multi_sim, simSlot + 1)
+ private CharSequence getTitleForCdmaPhone(int simSlot, boolean isPrimaryImei) {
+ int titleId = isPrimaryImei ? R.string.meid_multi_sim_primary : R.string.meid_multi_sim;
+ return isMultiSim() ? mContext.getString(titleId, simSlot + 1)
: mContext.getString(R.string.status_meid_number);
}
- private CharSequence getTitle(int simSlot) {
- final int phoneType = getPhoneType(simSlot);
- return phoneType == PHONE_TYPE_CDMA ? getTitleForCdmaPhone(simSlot)
- : getTitleForGsmPhone(simSlot);
+ protected boolean isPrimaryImei(int simSlot) {
+ CharSequence imei = getSummary(simSlot);
+ if (imei == null) {
+ return false;
+ }
+ String primaryImei = null;
+ try {
+ primaryImei = mTelephonyManager.getPrimaryImei();
+ } catch (Exception exception) {
+ Log.i(TAG, "PrimaryImei not available. " + exception);
+ }
+ return (primaryImei != null) && primaryImei.equals(imei.toString());
}
- private int getPhoneType(int slotIndex) {
- SubscriptionInfo subInfo = SubscriptionManager.from(mContext)
- .getActiveSubscriptionInfoForSimSlotIndex(slotIndex);
+ private CharSequence getTitle(int simSlot) {
+ boolean isPrimaryImei = isMultiSim() && isPrimaryImei(simSlot);
+ final int phoneType = getPhoneType(simSlot);
+ return phoneType == PHONE_TYPE_CDMA ? getTitleForCdmaPhone(simSlot, isPrimaryImei)
+ : getTitleForGsmPhone(simSlot, isPrimaryImei);
+ }
+
+ public int getPhoneType(int slotIndex) {
+ SubscriptionInfo subInfo = getSubscriptionInfo(slotIndex);
return mTelephonyManager.getCurrentPhoneType(subInfo != null ? subInfo.getSubscriptionId()
: SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
}
diff --git a/src/com/android/settings/display/ScreenResolutionController.java b/src/com/android/settings/display/ScreenResolutionController.java
index dca12757e18..a3433d405a5 100644
--- a/src/com/android/settings/display/ScreenResolutionController.java
+++ b/src/com/android/settings/display/ScreenResolutionController.java
@@ -17,7 +17,9 @@
package com.android.settings.display;
import android.content.Context;
+import android.graphics.Point;
import android.hardware.display.DisplayManager;
+import android.util.Log;
import android.view.Display;
import androidx.annotation.VisibleForTesting;
@@ -25,32 +27,63 @@ import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
/** Controller that switch the screen resolution. */
public class ScreenResolutionController extends BasePreferenceController {
-
- static final int FHD_WIDTH = 1080;
- static final int QHD_WIDTH = 1440;
+ private static final String TAG = "ScreenResolutionController";
+ static final int HIGHRESOLUTION_IDX = 0;
+ static final int FULLRESOLUTION_IDX = 1;
private Display mDisplay;
+ private Set mSupportedResolutions = null;
+ private int mHighWidth = 0;
+ private int mFullWidth = 0;
+ private int mHighHeight = 0;
+ private int mFullHeight = 0;
public ScreenResolutionController(Context context, String key) {
super(context, key);
mDisplay =
mContext.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY);
+
+ initSupportedResolutionData();
}
- /** Check if the width is supported by the display. */
- private boolean isSupportedMode(int width) {
+ /**
+ * Initialize the resolution data. So far, we support two resolution switching. Save the width
+ * and the height for high resolution and full resolution.
+ */
+ private void initSupportedResolutionData() {
+ // Collect and filter the resolutions
+ Set resolutions = new HashSet<>();
for (Display.Mode mode : getSupportedModes()) {
- if (mode.getPhysicalWidth() == width) return true;
+ resolutions.add(new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight()));
}
- return false;
+ mSupportedResolutions = resolutions;
+
+ // Get the width and height for high resolution and full resolution
+ List resolutionList = new ArrayList<>(resolutions);
+ if (resolutionList == null || resolutionList.size() != 2) {
+ Log.e(TAG, "No support");
+ return;
+ }
+
+ Collections.sort(resolutionList, (p1, p2) -> p1.x * p1.y - p2.x * p2.y);
+ mHighWidth = resolutionList.get(HIGHRESOLUTION_IDX).x;
+ mHighHeight = resolutionList.get(HIGHRESOLUTION_IDX).y;
+ mFullWidth = resolutionList.get(FULLRESOLUTION_IDX).x;
+ mFullHeight = resolutionList.get(FULLRESOLUTION_IDX).y;
}
/** Return true if the device contains two (or more) resolutions. */
protected boolean checkSupportedResolutions() {
- return isSupportedMode(FHD_WIDTH) && isSupportedMode(QHD_WIDTH);
+ return getHighWidth() != 0 && getFullWidth() != 0;
}
@Override
@@ -61,20 +94,43 @@ public class ScreenResolutionController extends BasePreferenceController {
@Override
public CharSequence getSummary() {
String summary = null;
- switch (getDisplayWidth()) {
- case FHD_WIDTH:
- summary = mContext.getString(R.string.screen_resolution_summary_high);
- break;
- case QHD_WIDTH:
- summary = mContext.getString(R.string.screen_resolution_summary_highest);
- break;
- default:
- summary = mContext.getString(R.string.screen_resolution_title);
+ int width = getDisplayWidth();
+ if (width == mHighWidth) {
+ summary = mContext.getString(R.string.screen_resolution_option_high);
+ } else if (width == mFullWidth) {
+ summary = mContext.getString(R.string.screen_resolution_option_full);
+ } else {
+ summary = mContext.getString(R.string.screen_resolution_title);
}
return summary;
}
+ /** Return all supported resolutions of the device. */
+ public Set getAllSupportedResolutions() {
+ return this.mSupportedResolutions;
+ }
+
+ /** Return the high resolution width of the device. */
+ public int getHighWidth() {
+ return this.mHighWidth;
+ }
+
+ /** Return the full resolution width of the device. */
+ public int getFullWidth() {
+ return this.mFullWidth;
+ }
+
+ /** Return the high resolution height of the device. */
+ public int getHighHeight() {
+ return this.mHighHeight;
+ }
+
+ /** Return the full resolution height of the device. */
+ public int getFullHeight() {
+ return this.mFullHeight;
+ }
+
@VisibleForTesting
public int getDisplayWidth() {
return mDisplay.getMode().getPhysicalWidth();
diff --git a/src/com/android/settings/display/ScreenResolutionFragment.java b/src/com/android/settings/display/ScreenResolutionFragment.java
index 665f6b08d69..687fdb88fea 100644
--- a/src/com/android/settings/display/ScreenResolutionFragment.java
+++ b/src/com/android/settings/display/ScreenResolutionFragment.java
@@ -16,9 +16,6 @@
package com.android.settings.display;
-import static com.android.settings.display.ScreenResolutionController.FHD_WIDTH;
-import static com.android.settings.display.ScreenResolutionController.QHD_WIDTH;
-
import android.annotation.Nullable;
import android.app.settings.SettingsEnums;
import android.content.Context;
@@ -48,7 +45,6 @@ import com.android.settingslib.widget.IllustrationPreference;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@@ -59,8 +55,6 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
private static final String TAG = "ScreenResolution";
private Resources mResources;
- private static final int FHD_INDEX = 0;
- private static final int QHD_INDEX = 1;
private static final String SCREEN_RESOLUTION = "user_selected_resolution";
private Display mDefaultDisplay;
private String[] mScreenResolutionOptions;
@@ -71,6 +65,9 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
private DisplayObserver mDisplayObserver;
private AccessibilityManager mAccessibilityManager;
+ private int mHighWidth;
+ private int mFullWidth;
+
@Override
public void onAttach(Context context) {
super.onAttach(context);
@@ -81,11 +78,18 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
mResources = context.getResources();
mScreenResolutionOptions =
mResources.getStringArray(R.array.config_screen_resolution_options_strings);
- mScreenResolutionSummaries =
- mResources.getStringArray(R.array.config_screen_resolution_summaries_strings);
- mResolutions = getAllSupportedResolution();
mImagePreference = new IllustrationPreference(context);
mDisplayObserver = new DisplayObserver(context);
+ ScreenResolutionController mController =
+ new ScreenResolutionController(context, "fragment");
+ mResolutions = mController.getAllSupportedResolutions();
+ mHighWidth = mController.getHighWidth();
+ mFullWidth = mController.getFullWidth();
+ mScreenResolutionSummaries =
+ new String[] {
+ mHighWidth + " x " + mController.getHighHeight(),
+ mFullWidth + " x " + mController.getFullHeight()
+ };
}
@Override
@@ -133,16 +137,6 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
return candidates;
}
- /** Get all supported resolutions on the device. */
- private Set getAllSupportedResolution() {
- Set resolutions = new HashSet<>();
- for (Display.Mode mode : mDefaultDisplay.getSupportedModes()) {
- resolutions.add(new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight()));
- }
-
- return resolutions;
- }
-
/** Get prefer display mode. */
private Display.Mode getPreferMode(int width) {
for (Point resolution : mResolutions) {
@@ -177,6 +171,7 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
try {
/** Apply the resolution change. */
+ Log.i(TAG, "setUserPreferredDisplayMode: " + mode);
mDefaultDisplay.setUserPreferredDisplayMode(mode);
} catch (Exception e) {
Log.e(TAG, "setUserPreferredDisplayMode() failed", e);
@@ -194,16 +189,19 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
/** Get the key corresponding to the resolution. */
@VisibleForTesting
String getKeyForResolution(int width) {
- return width == FHD_WIDTH
- ? mScreenResolutionOptions[FHD_INDEX]
- : width == QHD_WIDTH ? mScreenResolutionOptions[QHD_INDEX] : null;
+ return width == mHighWidth
+ ? mScreenResolutionOptions[ScreenResolutionController.HIGHRESOLUTION_IDX]
+ : width == mFullWidth
+ ? mScreenResolutionOptions[ScreenResolutionController.FULLRESOLUTION_IDX]
+ : null;
}
/** Get the width corresponding to the resolution key. */
int getWidthForResoluitonKey(String key) {
- return mScreenResolutionOptions[FHD_INDEX].equals(key)
- ? FHD_WIDTH
- : mScreenResolutionOptions[QHD_INDEX].equals(key) ? QHD_WIDTH : -1;
+ return mScreenResolutionOptions[ScreenResolutionController.HIGHRESOLUTION_IDX].equals(key)
+ ? mHighWidth
+ : mScreenResolutionOptions[ScreenResolutionController.FULLRESOLUTION_IDX].equals(
+ key) ? mFullWidth : -1;
}
@Override
@@ -248,9 +246,11 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
private void updateIllustrationImage(IllustrationPreference preference) {
String key = getDefaultKey();
- if (TextUtils.equals(mScreenResolutionOptions[FHD_INDEX], key)) {
+ if (TextUtils.equals(
+ mScreenResolutionOptions[ScreenResolutionController.HIGHRESOLUTION_IDX], key)) {
preference.setLottieAnimationResId(R.drawable.screen_resolution_1080p);
- } else if (TextUtils.equals(mScreenResolutionOptions[QHD_INDEX], key)) {
+ } else if (TextUtils.equals(
+ mScreenResolutionOptions[ScreenResolutionController.FULLRESOLUTION_IDX], key)) {
preference.setLottieAnimationResId(R.drawable.screen_resolution_1440p);
}
}
@@ -407,6 +407,8 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment {
return false;
}
+ Log.i(TAG,
+ "resolution changed from " + mPreviousWidth.get() + " to " + getCurrentWidth());
return true;
}
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
index 4b1bdf4eac3..004c8bf6638 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
@@ -233,6 +233,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
batteryCallbackData -> {
mBatteryUsageMap = batteryCallbackData.getBatteryUsageMap();
mScreenOnTimeMap = batteryCallbackData.getDeviceScreenOnTime();
+ logScreenUsageTime();
refreshUi();
});
Log.d(TAG, "getBatteryLevelData: " + batteryLevelData);
@@ -495,6 +496,42 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
};
}
+ private void logScreenUsageTime() {
+ if (mBatteryUsageMap == null || mScreenOnTimeMap == null) {
+ return;
+ }
+ final long totalScreenOnTime =
+ mScreenOnTimeMap
+ .get(BatteryChartViewModel.SELECTED_INDEX_ALL)
+ .get(BatteryChartViewModel.SELECTED_INDEX_ALL);
+ mMetricsFeatureProvider.action(
+ mPrefContext,
+ SettingsEnums.ACTION_BATTERY_USAGE_SCREEN_ON_TIME,
+ (int) totalScreenOnTime);
+ mMetricsFeatureProvider.action(
+ mPrefContext,
+ SettingsEnums.ACTION_BATTERY_USAGE_FOREGROUND_USAGE_TIME,
+ (int) getTotalForegroundUsageTime());
+ }
+
+ private long getTotalForegroundUsageTime() {
+ if (mBatteryUsageMap == null) {
+ return 0;
+ }
+ final BatteryDiffData totalBatteryUsageDiffData =
+ mBatteryUsageMap
+ .get(BatteryChartViewModel.SELECTED_INDEX_ALL)
+ .get(BatteryChartViewModel.SELECTED_INDEX_ALL);
+ if (totalBatteryUsageDiffData == null) {
+ return 0;
+ }
+ long totalValue = 0;
+ for (final BatteryDiffEntry entry : totalBatteryUsageDiffData.getAppDiffEntryList()) {
+ totalValue += entry.mForegroundUsageTimeInMs;
+ }
+ return totalValue;
+ }
+
private boolean isBatteryLevelDataInOneDay() {
return mHourlyViewModels != null && mHourlyViewModels.size() == 1;
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
index 649adbe8069..97363e07489 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
@@ -1792,6 +1792,8 @@ public final class DataProcessor {
systemAppsDiffEntry = new BatteryDiffEntry.SystemAppsBatteryDiffEntry(context);
}
systemAppsDiffEntry.mConsumePower += batteryDiffEntry.mConsumePower;
+ systemAppsDiffEntry.mForegroundUsageTimeInMs +=
+ batteryDiffEntry.mForegroundUsageTimeInMs;
systemAppsDiffEntry.setTotalConsumePower(
batteryDiffEntry.getTotalConsumePower());
appListIterator.remove();
diff --git a/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt b/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt
index 2d2dddf9f5d..43add1ffb91 100644
--- a/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt
+++ b/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt
@@ -19,7 +19,6 @@ package com.android.settings.spa.app.specialaccess
import android.Manifest
import android.app.AppGlobals
import android.app.AppOpsManager.MODE_DEFAULT
-import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES
import android.content.Context
import android.content.pm.ApplicationInfo
@@ -65,7 +64,6 @@ class InstallUnknownAppsListModel(private val context: Context) :
context = context,
app = app,
op = OP_REQUEST_INSTALL_PACKAGES,
- modeForNotAllowed = MODE_ERRORED
),
)
diff --git a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt
index 994ee92ba53..56c16df1dae 100644
--- a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt
+++ b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt
@@ -16,7 +16,6 @@
package com.android.settings.spa.app.specialaccess
-import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.OP_PICTURE_IN_PICTURE
import android.content.Context
import android.content.pm.ActivityInfo
@@ -85,7 +84,6 @@ class PictureInPictureListModel(private val context: Context) :
context = context,
app = app,
op = OP_PICTURE_IN_PICTURE,
- modeForNotAllowed = MODE_ERRORED,
),
)
diff --git a/src/com/android/settings/spa/app/specialaccess/WifiControlApps.kt b/src/com/android/settings/spa/app/specialaccess/WifiControlApps.kt
index 69f7b1e1688..50bb5c9b9f6 100644
--- a/src/com/android/settings/spa/app/specialaccess/WifiControlApps.kt
+++ b/src/com/android/settings/spa/app/specialaccess/WifiControlApps.kt
@@ -32,8 +32,8 @@ object WifiControlAppListProvider : TogglePermissionAppListProvider {
}
class WifiControlAppListModel(
- private val context: Context,
- private val packageManagers: IPackageManagers = PackageManagers
+ context: Context,
+ packageManagers: IPackageManagers = PackageManagers
) : AppOpPermissionListModel(context, packageManagers) {
override val pageTitleResId = R.string.change_wifi_state_title
override val switchTitleResId = R.string.change_wifi_state_app_detail_switch
@@ -44,6 +44,6 @@ class WifiControlAppListModel(
/** NETWORK_SETTINGS permission trumps CHANGE_WIFI_CONFIG. */
override val broaderPermission = Manifest.permission.NETWORK_SETTINGS
- override val permissionHasAppopFlag = false
+ override val permissionHasAppOpFlag = false
override val modeForNotAllowed = MODE_IGNORED
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java
index 7590d617075..bf4e055c493 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java
@@ -18,13 +18,24 @@ package com.android.settings.bluetooth;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.content.Intent;
import android.util.FeatureFlagUtils;
+import androidx.preference.Preference;
+
+import com.android.settings.SettingsActivity;
+import com.android.settings.accessibility.AccessibilityHearingAidsFragment;
+import com.android.settings.testutils.FakeFeatureFactory;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
@@ -36,14 +47,18 @@ public class BluetoothDetailsHearingDeviceControlsControllerTest extends
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
+ @Captor
+ private ArgumentCaptor mIntentArgumentCaptor;
private BluetoothDetailsHearingDeviceControlsController mController;
@Override
public void setUp() {
super.setUp();
- mController = new BluetoothDetailsHearingDeviceControlsController(mContext, mFragment,
+ FakeFeatureFactory.setupForTest();
+ mController = new BluetoothDetailsHearingDeviceControlsController(mActivity, mFragment,
mCachedDevice, mLifecycle);
+ when(mCachedDevice.isHearingAidDevice()).thenReturn(true);
}
@Test
@@ -63,4 +78,23 @@ public class BluetoothDetailsHearingDeviceControlsControllerTest extends
assertThat(mController.isAvailable()).isFalse();
}
+
+ @Test
+ public void onPreferenceClick_hearingDeviceControlsKey_LaunchExpectedFragment() {
+ final Preference hearingControlsKeyPreference = new Preference(mContext);
+ hearingControlsKeyPreference.setKey(
+ BluetoothDetailsHearingDeviceControlsController.KEY_HEARING_DEVICE_CONTROLS);
+
+ mController.onPreferenceClick(hearingControlsKeyPreference);
+
+ assertStartActivityWithExpectedFragment(mActivity,
+ AccessibilityHearingAidsFragment.class.getName());
+ }
+
+ private void assertStartActivityWithExpectedFragment(Context mockContext, String fragmentName) {
+ verify(mockContext).startActivity(mIntentArgumentCaptor.capture());
+ assertThat(mIntentArgumentCaptor.getValue()
+ .getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(fragmentName);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java
index be41787882a..d81722fd5fc 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java
@@ -101,6 +101,7 @@ public class StylusDevicesControllerTest {
when(mRm.getRoleHoldersAsUser(eq(RoleManager.ROLE_NOTES), any(UserHandle.class)))
.thenReturn(Collections.singletonList(NOTES_PACKAGE_NAME));
+ when(mRm.isRoleAvailable(RoleManager.ROLE_NOTES)).thenReturn(true);
when(mContext.getPackageManager()).thenReturn(mPm);
when(mPm.getApplicationInfo(eq(NOTES_PACKAGE_NAME),
any(PackageManager.ApplicationInfoFlags.class))).thenReturn(new ApplicationInfo());
@@ -235,16 +236,26 @@ public class StylusDevicesControllerTest {
}
@Test
- public void defaultNotesPreference_noRoleHolder_hidesNotesRoleApp() {
+ public void defaultNotesPreference_roleHolderChanges_updatesPreference() {
+ showScreen(mController);
+ Preference defaultNotesPref = mPreferenceContainer.getPreference(0);
when(mRm.getRoleHoldersAsUser(eq(RoleManager.ROLE_NOTES), any(UserHandle.class)))
.thenReturn(Collections.emptyList());
showScreen(mController);
- for (int i = 0; i < mPreferenceContainer.getPreferenceCount(); i++) {
- Preference pref = mPreferenceContainer.getPreference(i);
- assertThat(pref.getTitle().toString()).isNotEqualTo(
- mContext.getString(R.string.stylus_default_notes_app));
- }
+ assertThat(defaultNotesPref.getSummary().toString()).isEqualTo(
+ mContext.getString(R.string.default_app_none));
+ }
+
+ @Test
+ public void defaultNotesPreference_noRoleHolder_showNoneInSummary() {
+ when(mRm.getRoleHoldersAsUser(eq(RoleManager.ROLE_NOTES), any(UserHandle.class)))
+ .thenReturn(Collections.emptyList());
+ showScreen(mController);
+
+ Preference defaultNotesPref = mPreferenceContainer.getPreference(0);
+ assertThat(defaultNotesPref.getSummary().toString()).isEqualTo(
+ mContext.getString(R.string.default_app_none));
}
@Test
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceControllerTest.java
index f7970cf9ce8..f3a935820ac 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceControllerTest.java
@@ -18,10 +18,9 @@ package com.android.settings.deviceinfo.imei;
import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
import static android.telephony.TelephonyManager.PHONE_TYPE_GSM;
-
+import static android.telephony.TelephonyManager.PHONE_TYPE_NONE;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
-
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -32,15 +31,13 @@ import android.content.Context;
import android.content.res.Resources;
import android.os.UserManager;
import android.telephony.TelephonyManager;
-
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
-
import com.android.settings.R;
-
+import com.android.settings.deviceinfo.simstatus.SlotSimStatus;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -50,7 +47,6 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
-import org.robolectric.util.ReflectionHelpers;
@RunWith(RobolectricTestRunner.class)
public class ImeiInfoPreferenceControllerTest {
@@ -72,7 +68,9 @@ public class ImeiInfoPreferenceControllerTest {
private Context mContext;
private Resources mResources;
+ private ImeiInfoPreferenceController mDefaultController;
private ImeiInfoPreferenceController mController;
+ private ImeiInfoPreferenceController mSecondController;
@Before
public void setUp() {
@@ -83,56 +81,77 @@ public class ImeiInfoPreferenceControllerTest {
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getBoolean(R.bool.config_show_sim_info)).thenReturn(true);
- doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
- mController = spy(new ImeiInfoPreferenceController(mContext, "imei_info"));
- mController.setHost(mFragment);
- doReturn(AVAILABLE).when(mController).getAvailabilityStatus();
+ mockService(Context.TELEPHONY_SERVICE, TelephonyManager.class, mTelephonyManager);
+ mockService(Context.USER_SERVICE, UserManager.class, mUserManager);
+
when(mScreen.getContext()).thenReturn(mContext);
- doReturn(mSecondSimPreference).when(mController).createNewPreference(mContext);
final String categoryKey = "device_detail_category";
when(mScreen.findPreference(categoryKey)).thenReturn(mCategory);
- ReflectionHelpers.setField(mController, "mTelephonyManager", mTelephonyManager);
- when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
- final String prefKey = mController.getPreferenceKey();
- when(mPreference.getKey()).thenReturn(prefKey);
- when(mPreference.isVisible()).thenReturn(true);
}
- @Test
- public void displayPreference_multiSimGsm_shouldAddSecondPreference() {
- ReflectionHelpers.setField(mController, "mIsMultiSim", true);
- when(mTelephonyManager.getPhoneCount()).thenReturn(2);
- when(mTelephonyManager.getCurrentPhoneType(anyInt())).thenReturn(PHONE_TYPE_GSM);
+ private ImeiInfoPreferenceController createPreferenceController(SlotSimStatus slotSimStatus,
+ String key, Preference preference, int phoneType) {
+ ImeiInfoPreferenceController controller =
+ spy(new ImeiInfoPreferenceController(mContext, key) {
+ public int getPhoneType(int slotId) {
+ return phoneType;
+ }
+ });
+ controller.init(mFragment, slotSimStatus);
+ doReturn(AVAILABLE).when(controller).getAvailabilityStatus();
+ doReturn(preference).when(controller).createNewPreference(mContext);
- mController.displayPreference(mScreen);
+ when(mScreen.findPreference(key)).thenReturn(preference);
+ when(preference.getKey()).thenReturn(key);
+ when(preference.isVisible()).thenReturn(true);
+ return controller;
+ }
- verify(mCategory).addPreference(mSecondSimPreference);
+ private void setupPhoneCount(int count, int phoneType1, int phoneType2) {
+ when(mTelephonyManager.getPhoneCount()).thenReturn(count);
+
+ SlotSimStatus slotSimStatus = new SlotSimStatus(mContext);
+ mController = createPreferenceController(slotSimStatus,
+ "imei_info1", mPreference, phoneType1);
+ mSecondController = createPreferenceController(slotSimStatus,
+ "imei_info2", mSecondSimPreference, phoneType2);
}
@Ignore
+ @Test
+ public void displayPreference_multiSimGsm_shouldAddSecondPreference() {
+ setupPhoneCount(2, PHONE_TYPE_GSM, PHONE_TYPE_GSM);
+
+ mDefaultController.displayPreference(mScreen);
+
+ verify(mCategory).addPreference(mSecondSimPreference);
+ }
+
@Test
public void displayPreference_singleSimCdmaPhone_shouldSetSingleSimCdmaTitleAndMeid() {
- ReflectionHelpers.setField(mController, "mIsMultiSim", false);
- final String meid = "125132215123";
- when(mTelephonyManager.getCurrentPhoneType(anyInt())).thenReturn(PHONE_TYPE_CDMA);
+ setupPhoneCount(1, PHONE_TYPE_CDMA, PHONE_TYPE_NONE);
+
+ final String meid = "Tap to show info";
when(mTelephonyManager.getMeid(anyInt())).thenReturn(meid);
mController.displayPreference(mScreen);
+ mController.updateState(mPreference);
verify(mPreference).setTitle(mContext.getString(R.string.status_meid_number));
verify(mPreference).setSummary(meid);
}
- @Ignore
@Test
public void displayPreference_multiSimCdmaPhone_shouldSetMultiSimCdmaTitleAndMeid() {
- ReflectionHelpers.setField(mController, "mIsMultiSim", true);
- final String meid = "125132215123";
- when(mTelephonyManager.getPhoneCount()).thenReturn(2);
- when(mTelephonyManager.getCurrentPhoneType(anyInt())).thenReturn(PHONE_TYPE_CDMA);
+ setupPhoneCount(2, PHONE_TYPE_CDMA, PHONE_TYPE_CDMA);
+
+ final String meid = "Tap to show info";
when(mTelephonyManager.getMeid(anyInt())).thenReturn(meid);
mController.displayPreference(mScreen);
+ mController.updateState(mPreference);
+ mSecondController.displayPreference(mScreen);
+ mSecondController.updateState(mSecondSimPreference);
verify(mPreference).setTitle(mContext.getString(R.string.meid_multi_sim, 1 /* sim slot */));
verify(mSecondSimPreference).setTitle(
@@ -141,30 +160,31 @@ public class ImeiInfoPreferenceControllerTest {
verify(mSecondSimPreference).setSummary(meid);
}
- @Ignore
@Test
public void displayPreference_singleSimGsmPhone_shouldSetSingleSimGsmTitleAndImei() {
- ReflectionHelpers.setField(mController, "mIsMultiSim", false);
- final String imei = "125132215123";
- when(mTelephonyManager.getCurrentPhoneType(anyInt())).thenReturn(PHONE_TYPE_GSM);
+ setupPhoneCount(1, PHONE_TYPE_GSM, PHONE_TYPE_NONE);
+
+ final String imei = "Tap to show info";
when(mTelephonyManager.getImei(anyInt())).thenReturn(imei);
mController.displayPreference(mScreen);
+ mController.updateState(mPreference);
verify(mPreference).setTitle(mContext.getString(R.string.status_imei));
verify(mPreference).setSummary(imei);
}
- @Ignore
@Test
public void displayPreference_multiSimGsmPhone_shouldSetMultiSimGsmTitleAndImei() {
- ReflectionHelpers.setField(mController, "mIsMultiSim", true);
- final String imei = "125132215123";
- when(mTelephonyManager.getPhoneCount()).thenReturn(2);
- when(mTelephonyManager.getCurrentPhoneType(anyInt())).thenReturn(PHONE_TYPE_GSM);
+ setupPhoneCount(2, PHONE_TYPE_GSM, PHONE_TYPE_GSM);
+
+ final String imei = "Tap to show info";
when(mTelephonyManager.getImei(anyInt())).thenReturn(imei);
mController.displayPreference(mScreen);
+ mController.updateState(mPreference);
+ mSecondController.displayPreference(mScreen);
+ mSecondController.updateState(mSecondSimPreference);
verify(mPreference).setTitle(mContext.getString(R.string.imei_multi_sim, 1 /* sim slot */));
verify(mSecondSimPreference).setTitle(
@@ -175,6 +195,8 @@ public class ImeiInfoPreferenceControllerTest {
@Test
public void handlePreferenceTreeClick_shouldStartDialogFragment() {
+ setupPhoneCount(1, PHONE_TYPE_GSM, PHONE_TYPE_NONE);
+
when(mFragment.getChildFragmentManager())
.thenReturn(mock(FragmentManager.class, Answers.RETURNS_DEEP_STUBS));
when(mPreference.getTitle()).thenReturn("SomeTitle");
@@ -184,4 +206,9 @@ public class ImeiInfoPreferenceControllerTest {
verify(mFragment).getChildFragmentManager();
}
+
+ private void mockService(String serviceName, Class serviceClass, T service) {
+ when(mContext.getSystemServiceName(serviceClass)).thenReturn(serviceName);
+ when(mContext.getSystemService(serviceName)).thenReturn(service);
+ }
}
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
index 24f4f89e5bc..b074090afae 100644
--- a/tests/uitests/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivityTest.java
+++ b/tests/uitests/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivityTest.java
@@ -58,26 +58,30 @@ public class FingerprintEnrollmentActivityTest {
}
@Test
- public void lunchWithoutCredential() {
+ public void launchWithoutCredential() {
launchFingerprintEnrollActivity(true);
Assert.assertNotNull(mDevice.wait(Until.hasObject(
By.text("Choose your backup screen lock method")), IDLE_TIMEOUT));
}
@Test
- public void lunchWithCredential() {
+ public void launchWithCredential() {
LockScreenUtil.setLockscreen(LockScreenUtil.LockscreenType.PIN, "1234", true);
launchFingerprintEnrollActivity(true);
- Assert.assertNotNull(mDevice.wait(Until.hasObject(
- By.text("More")), IDLE_TIMEOUT));
+ for (long i = 0; i < IDLE_TIMEOUT; i += 100L) {
+ if (mDevice.wait(Until.hasObject(By.text("More")), 50L) != null) {
+ break;
+ } else if (mDevice.wait(Until.hasObject(By.text("I agree")), 50L) != null) {
+ break;
+ }
+ }
- //click more btn twice and the introduction should stay in the last page
- UiObject2 moreBtn = mDevice.findObject(By.text("More"));
- moreBtn.click();
- Assert.assertNotNull(mDevice.wait(Until.hasObject(
- By.text("More")), IDLE_TIMEOUT));
- moreBtn = mDevice.findObject(By.text("More"));
- moreBtn.click();
+ //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.wait(Until.hasObject(By.text("More")), IDLE_TIMEOUT);
+ }
Assert.assertNotNull(mDevice.wait(Until.hasObject(
By.text("I agree")), IDLE_TIMEOUT));
@@ -114,5 +118,4 @@ public class FingerprintEnrollmentActivityTest {
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
-
}
diff --git a/tests/unit/src/com/android/settings/biometrics2/data/repository/FingerprintRepositoryTest.java b/tests/unit/src/com/android/settings/biometrics2/data/repository/FingerprintRepositoryTest.java
index e5920f35b4a..6fb86903316 100644
--- a/tests/unit/src/com/android/settings/biometrics2/data/repository/FingerprintRepositoryTest.java
+++ b/tests/unit/src/com/android/settings/biometrics2/data/repository/FingerprintRepositoryTest.java
@@ -23,16 +23,17 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFP
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UNKNOWN;
-import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintEnrolledFingerprints;
-import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintFirstSensor;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
@@ -48,6 +49,8 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.ArrayList;
+
@RunWith(AndroidJUnit4.class)
public class FingerprintRepositoryTest {
@@ -66,31 +69,49 @@ public class FingerprintRepositoryTest {
}
@Test
- public void testCanAssumeSensorType() {
+ public void testCanAssumeSensorType_forUnknownSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 1);
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
+ assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
+ }
+ @Test
+ public void testCanAssumeSensorType_forRearSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_REAR, 1);
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
+ assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
+ }
+ @Test
+ public void testCanAssumeSensorType_forUdfpsUltrasonicSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_ULTRASONIC, 1);
assertThat(mFingerprintRepository.canAssumeUdfps()).isTrue();
+ assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
+ }
+ @Test
+ public void testCanAssumeSensorType_forUdfpsOpticalSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_OPTICAL, 1);
assertThat(mFingerprintRepository.canAssumeUdfps()).isTrue();
+ assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
+ }
+ @Test
+ public void testCanAssumeSensorType_forPowerButtonSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_POWER_BUTTON, 1);
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
+ assertThat(mFingerprintRepository.canAssumeSfps()).isTrue();
+ }
+ @Test
+ public void testCanAssumeSensorType_forHomeButtonSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_HOME_BUTTON, 1);
assertThat(mFingerprintRepository.canAssumeUdfps()).isFalse();
+ assertThat(mFingerprintRepository.canAssumeSfps()).isFalse();
}
@Test
public void testGetMaxFingerprints() {
- setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 44);
- assertThat(mFingerprintRepository.getMaxFingerprints()).isEqualTo(44);
-
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UNKNOWN, 999);
assertThat(mFingerprintRepository.getMaxFingerprints()).isEqualTo(999);
}
@@ -122,4 +143,31 @@ public class FingerprintRepositoryTest {
"suw_max_fingerprints_enrollable");
when(mockedResources.getInteger(resId)).thenReturn(numOfFp);
}
+
+ public static void setupFingerprintFirstSensor(
+ @NonNull FingerprintManager mockedFingerprintManager,
+ @FingerprintSensorProperties.SensorType int sensorType,
+ int maxEnrollmentsPerUser) {
+
+ final ArrayList props = new ArrayList<>();
+ props.add(new FingerprintSensorPropertiesInternal(
+ 0 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ maxEnrollmentsPerUser,
+ new ArrayList<>() /* componentInfo */,
+ sensorType,
+ true /* resetLockoutRequiresHardwareAuthToken */));
+ when(mockedFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
+ }
+
+ public static void setupFingerprintEnrolledFingerprints(
+ @NonNull FingerprintManager mockedFingerprintManager,
+ int userId,
+ int enrolledFingerprints) {
+ final ArrayList ret = new ArrayList<>();
+ for (int i = 0; i < enrolledFingerprints; ++i) {
+ ret.add(new Fingerprint("name", 0, 0, 0L));
+ }
+ when(mockedFingerprintManager.getEnrolledFingerprints(userId)).thenReturn(ret);
+ }
}
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java
index 4db005ec609..024607d7381 100644
--- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java
@@ -45,6 +45,7 @@ import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_G
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
@@ -71,6 +72,8 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.concurrent.atomic.AtomicBoolean;
+
@RunWith(AndroidJUnit4.class)
public class AutoCredentialViewModelTest {
@@ -299,13 +302,18 @@ public class AutoCredentialViewModelTest {
when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
.thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
+ final AtomicBoolean hasCalledRemoveGkPwHandle = new AtomicBoolean();
+ doAnswer(invocation -> {
+ hasCalledRemoveGkPwHandle.set(true);
+ return null;
+ }).when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle);
+
// Run credential check
@CredentialAction final int action = mViewModel.checkCredential();
// Check viewModel behavior
assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE);
assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
- assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
// Check data inside CredentialModel
final Bundle extras = mViewModel.createCredentialIntentExtra();
@@ -314,6 +322,8 @@ public class AutoCredentialViewModelTest {
assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull();
assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE);
assertThat(extras.getLong(EXTRA_KEY_CHALLENGE)).isNotEqualTo(INVALID_CHALLENGE);
+ assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
+ assertThat(hasCalledRemoveGkPwHandle.get()).isFalse();
// Check createGeneratingChallengeExtras()
final Bundle generatingChallengeExtras = mViewModel.createGeneratingChallengeExtras();
@@ -511,6 +521,12 @@ public class AutoCredentialViewModelTest {
when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
.thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
+ final AtomicBoolean hasCalledRemoveGkPwHandle = new AtomicBoolean();
+ doAnswer(invocation -> {
+ hasCalledRemoveGkPwHandle.set(true);
+ return null;
+ }).when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle);
+
// Run checkNewCredentialFromActivityResult()
final Intent intent = new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
final boolean ret = mViewModel.checkNewCredentialFromActivityResult(true,
@@ -524,6 +540,7 @@ public class AutoCredentialViewModelTest {
assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull();
assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE);
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
+ assertThat(hasCalledRemoveGkPwHandle.get()).isTrue();
}
@Test
@@ -541,6 +558,12 @@ public class AutoCredentialViewModelTest {
when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
.thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
+ final AtomicBoolean hasCalledRemoveGkPwHandle = new AtomicBoolean();
+ doAnswer(invocation -> {
+ hasCalledRemoveGkPwHandle.set(true);
+ return null;
+ }).when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle);
+
// Run checkNewCredentialFromActivityResult()
final Intent intent = new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
final boolean ret = mViewModel.checkNewCredentialFromActivityResult(false,
@@ -554,6 +577,7 @@ public class AutoCredentialViewModelTest {
assertThat(extras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN)).isNotNull();
assertThat(extras.getLong(EXTRA_KEY_GK_PW_HANDLE)).isEqualTo(INVALID_GK_PW_HANDLE);
assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
+ assertThat(hasCalledRemoveGkPwHandle.get()).isTrue();
}
public static class TestChallengeGenerator implements ChallengeGenerator {
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java
index a9536cffcbf..192a3050088 100644
--- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java
@@ -20,6 +20,8 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
+import static com.android.settings.biometrics2.data.repository.FingerprintRepositoryTest.setupFingerprintEnrolledFingerprints;
+import static com.android.settings.biometrics2.data.repository.FingerprintRepositoryTest.setupFingerprintFirstSensor;
import static com.android.settings.biometrics2.data.repository.FingerprintRepositoryTest.setupSuwMaxFingerprintsEnrollable;
import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX;
import static com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus.FINGERPRINT_ENROLLABLE_OK;
@@ -32,8 +34,6 @@ import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsS
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwPortalRequest;
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwRequest;
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwSuggestedActionFlowRequest;
-import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintEnrolledFingerprints;
-import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintFirstSensor;
import static com.google.common.truth.Truth.assertThat;
@@ -134,26 +134,47 @@ public class FingerprintEnrollIntroViewModelTest {
}
@Test
- public void testOnStartToUpdateEnrollableStatus_isNotSuw() {
- testOnStartToUpdateEnrollableStatus(newAllFalseRequest(mApplication));
+ public void testOnStartToUpdateEnrollableStatusOk_isNotSuw() {
+ testOnStartToUpdateEnrollableStatusOk(newAllFalseRequest(mApplication));
}
@Test
- public void testOnStartToUpdateEnrollableStatus_isSuwDeferred() {
- testOnStartToUpdateEnrollableStatus(newIsSuwDeferredRequest(mApplication));
+ public void testOnStartToUpdateEnrollableStatusReachMax_isNotSuw() {
+ testOnStartToUpdateEnrollableStatusReachMax(newAllFalseRequest(mApplication));
}
@Test
- public void testOnStartToUpdateEnrollableStatus_isSuwPortal() {
- testOnStartToUpdateEnrollableStatus(newIsSuwPortalRequest(mApplication));
+ public void testOnStartToUpdateEnrollableStatusOk_isSuwDeferred() {
+ testOnStartToUpdateEnrollableStatusOk(newIsSuwDeferredRequest(mApplication));
}
@Test
- public void testOnStartToUpdateEnrollableStatus_isSuwSuggestedActionFlow() {
- testOnStartToUpdateEnrollableStatus(newIsSuwSuggestedActionFlowRequest(mApplication));
+ public void testOnStartToUpdateEnrollableStatusReachMax_isSuwDeferred() {
+ testOnStartToUpdateEnrollableStatusReachMax(newIsSuwDeferredRequest(mApplication));
}
- private void testOnStartToUpdateEnrollableStatus(@NonNull EnrollmentRequest request) {
+ @Test
+ public void testOnStartToUpdateEnrollableStatusOk_isSuwPortal() {
+ testOnStartToUpdateEnrollableStatusOk(newIsSuwPortalRequest(mApplication));
+ }
+
+ @Test
+ public void testOnStartToUpdateEnrollableStatusReachMax_isSuwPortal() {
+ testOnStartToUpdateEnrollableStatusReachMax(newIsSuwPortalRequest(mApplication));
+ }
+
+ @Test
+ public void testOnStartToUpdateEnrollableStatusOk_isSuwSuggestedActionFlow() {
+ testOnStartToUpdateEnrollableStatusOk(newIsSuwSuggestedActionFlowRequest(mApplication));
+ }
+
+ @Test
+ public void testOnStartToUpdateEnrollableStatusReachMax_isSuwSuggestedActionFlow() {
+ testOnStartToUpdateEnrollableStatusReachMax(
+ newIsSuwSuggestedActionFlowRequest(mApplication));
+ }
+
+ private void testOnStartToUpdateEnrollableStatusOk(@NonNull EnrollmentRequest request) {
final int userId = 45;
mViewModel.setUserId(userId);
mViewModel.setEnrollmentRequest(request);
@@ -163,19 +184,28 @@ public class FingerprintEnrollIntroViewModelTest {
mViewModel.onStart(mLifecycleOwner);
FingerprintEnrollIntroStatus status = mViewModel.getPageStatusLiveData().getValue();
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
+ }
+
+ private void testOnStartToUpdateEnrollableStatusReachMax(@NonNull EnrollmentRequest request) {
+ final int userId = 45;
+ mViewModel.setUserId(userId);
+ mViewModel.setEnrollmentRequest(request);
setupFingerprintEnrolledFingerprints(mFingerprintManager, userId, 5);
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5);
mViewModel.onStart(mLifecycleOwner);
- status = mViewModel.getPageStatusLiveData().getValue();
+ FingerprintEnrollIntroStatus status = mViewModel.getPageStatusLiveData().getValue();
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX);
}
@Test
- public void textCanAssumeUdfps() {
+ public void textCanAssumeUdfps_forUdfpsUltrasonicSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_UDFPS_ULTRASONIC, 1);
assertThat(mViewModel.canAssumeUdfps()).isEqualTo(true);
+ }
+ @Test
+ public void textCanAssumeUdfps_forRearSensor() {
setupFingerprintFirstSensor(mFingerprintManager, TYPE_REAR, 1);
assertThat(mViewModel.canAssumeUdfps()).isEqualTo(false);
}
@@ -238,7 +268,7 @@ public class FingerprintEnrollIntroViewModelTest {
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
// Perform click on `next`
- mViewModel.onNextButtonClick(null);
+ mViewModel.onNextButtonClick();
assertThat(mViewModel.getActionLiveData().getValue())
.isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL);
@@ -258,7 +288,7 @@ public class FingerprintEnrollIntroViewModelTest {
assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX);
// Perform click on `next`
- mViewModel.onNextButtonClick(null);
+ mViewModel.onNextButtonClick();
assertThat(mViewModel.getActionLiveData().getValue())
.isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH);
@@ -266,7 +296,7 @@ public class FingerprintEnrollIntroViewModelTest {
@Test
public void testOnSkipOrCancelButtonClick() {
- mViewModel.onSkipOrCancelButtonClick(null);
+ mViewModel.onSkipOrCancelButtonClick();
assertThat(mViewModel.getActionLiveData().getValue())
.isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL);
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModelTest.java
index b1d55aa332d..736e1ae5b39 100644
--- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModelTest.java
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModelTest.java
@@ -20,10 +20,10 @@ import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHE
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_SKIP;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT;
import static com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction.EXTRA_FINGERPRINT_ENROLLED_COUNT;
+import static com.android.settings.biometrics2.data.repository.FingerprintRepositoryTest.setupFingerprintEnrolledFingerprints;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel.SAVED_STATE_IS_WAITING_ACTIVITY_RESULT;
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newAllFalseRequest;
import static com.android.settings.biometrics2.util.EnrollmentRequestUtil.newIsSuwRequest;
-import static com.android.settings.biometrics2.util.FingerprintManagerUtil.setupFingerprintEnrolledFingerprints;
import static com.google.common.truth.Truth.assertThat;
diff --git a/tests/unit/src/com/android/settings/biometrics2/util/FingerprintManagerUtil.java b/tests/unit/src/com/android/settings/biometrics2/util/FingerprintManagerUtil.java
deleted file mode 100644
index cb45fa4c6c2..00000000000
--- a/tests/unit/src/com/android/settings/biometrics2/util/FingerprintManagerUtil.java
+++ /dev/null
@@ -1,58 +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.util;
-
-import static org.mockito.Mockito.when;
-
-import android.hardware.biometrics.SensorProperties;
-import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-
-import androidx.annotation.NonNull;
-
-import java.util.ArrayList;
-
-public class FingerprintManagerUtil {
-
- public static void setupFingerprintFirstSensor(
- @NonNull FingerprintManager mockedFingerprintManager,
- @FingerprintSensorProperties.SensorType int sensorType,
- int maxEnrollmentsPerUser) {
- final ArrayList props = new ArrayList<>();
- props.add(new FingerprintSensorPropertiesInternal(
- 0 /* sensorId */,
- SensorProperties.STRENGTH_STRONG,
- maxEnrollmentsPerUser,
- new ArrayList<>() /* componentInfo */,
- sensorType,
- true /* resetLockoutRequiresHardwareAuthToken */));
- when(mockedFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
- }
-
- public static void setupFingerprintEnrolledFingerprints(
- @NonNull FingerprintManager mockedFingerprintManager,
- int userId,
- int enrolledFingerprints) {
- final ArrayList ret = new ArrayList<>();
- for (int i = 0; i < enrolledFingerprints; ++i) {
- ret.add(new Fingerprint("name", 0, 0, 0L));
- }
- when(mockedFingerprintManager.getEnrolledFingerprints(userId)).thenReturn(ret);
- }
-}
diff --git a/tests/unit/src/com/android/settings/communal/CommunalPreferenceControllerTest.java b/tests/unit/src/com/android/settings/communal/CommunalPreferenceControllerTest.java
new file mode 100644
index 00000000000..e8421d0b826
--- /dev/null
+++ b/tests/unit/src/com/android/settings/communal/CommunalPreferenceControllerTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.communal;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.UserManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.testutils.ResourcesUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class CommunalPreferenceControllerTest {
+ @Mock
+ private UserManager mUserManager;
+
+ private Resources mResources;
+ private CommunalPreferenceController mController;
+
+ private static final String PREF_KEY = "test_key";
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ final Context context = spy(ApplicationProvider.getApplicationContext());
+ mResources = spy(context.getResources());
+
+ mController = new CommunalPreferenceController(context, PREF_KEY);
+
+ when(context.getResources()).thenReturn(mResources);
+ when(context.getSystemService(UserManager.class)).thenReturn(mUserManager);
+ }
+
+ @Test
+ public void isAvailable_communalEnabled_shouldBeTrueForSystemUser() {
+ setCommunalEnabled(true);
+ when(mUserManager.isSystemUser()).thenReturn(true);
+ assertTrue(mController.isAvailable());
+ }
+
+ @Test
+ public void isAvailable_communalEnabled_shouldBeFalseForNonSystemUser() {
+ setCommunalEnabled(true);
+ when(mUserManager.isSystemUser()).thenReturn(false);
+ assertFalse(mController.isAvailable());
+ }
+
+ @Test
+ public void isAvailable_communalDisabled_shouldBeFalseForSystemUser() {
+ setCommunalEnabled(false);
+ when(mUserManager.isSystemUser()).thenReturn(true);
+ assertFalse(mController.isAvailable());
+ }
+
+ private void setCommunalEnabled(boolean enabled) {
+ final int boolId = ResourcesUtils.getResourcesId(
+ ApplicationProvider.getApplicationContext(), "bool",
+ "config_show_communal_settings");
+ when(mResources.getBoolean(boolId)).thenReturn(enabled);
+ }
+}