Merge "Add a test for the NPE case" into main
diff --git a/aconfig/Android.bp b/aconfig/Android.bp
index 6daa90c..7b571e0 100644
--- a/aconfig/Android.bp
+++ b/aconfig/Android.bp
@@ -12,6 +12,7 @@
"settings_experience_flag_declarations.aconfig",
"settings_onboarding_experience_flag_declarations.aconfig",
"settings_telephony_flag_declarations.aconfig",
+ "settings_biometrics_integration_declarations.aconfig",
],
}
diff --git a/aconfig/settings_biometrics_integration_declarations.aconfig b/aconfig/settings_biometrics_integration_declarations.aconfig
new file mode 100644
index 0000000..bc437f2
--- /dev/null
+++ b/aconfig/settings_biometrics_integration_declarations.aconfig
@@ -0,0 +1,16 @@
+package: "com.android.settings.flags"
+
+flag {
+ name: "sfps_enroll_refinement"
+ namespace: "biometrics_integration"
+ description: "This flag controls whether the sfps pause enrollment feature should be enabled"
+ bug: "288155127"
+}
+
+flag {
+ name: "udfps_enroll_calibration"
+ namespace: "biometrics_integration"
+ description: "This flag controls whether the fps enroll calibration feature should be enabled"
+ bug: "301226085"
+}
+
diff --git a/res/layout/fingerprint_v2_enroll_introduction.xml b/res/layout/fingerprint_v2_enroll_introduction.xml
index cf39206..2fd1f9c 100644
--- a/res/layout/fingerprint_v2_enroll_introduction.xml
+++ b/res/layout/fingerprint_v2_enroll_introduction.xml
@@ -24,7 +24,6 @@
<LinearLayout
style="@style/SudContentFrame"
- android:id="@+id/enroll_intro_content_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml
index f69c952..a93c981 100644
--- a/res/values-night/styles.xml
+++ b/res/values-night/styles.xml
@@ -19,15 +19,4 @@
<style name="Widget.ActionBar.Base" parent="@android:style/Widget.DeviceDefault.ActionBar.Solid">
<item name="android:background">?android:attr/colorPrimaryDark</item>
</style>
-
- <style name="TextAppearance.SimConfirmDialogList" parent="@style/TextAppearance.DialogMessage">
- <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
- </style>
-
- <style name="TextAppearance.SimConfirmDialogList.Summary">
- <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
- </style>
</resources>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 145c6c5..4eba076 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -226,6 +226,9 @@
Can be overridden for specific product builds if the target device does not support it -->
<bool name="config_media_vibration_supported">true</bool>
+ <!-- Whether to show Keyboard vibration settings in the vibration and haptics screen. -->
+ <bool name="config_keyboard_vibration_supported">false</bool>
+
<!--
Whether or not the homepage should be powered by legacy suggestion (versus contextual cards)
Default to true as not all devices support contextual cards.
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 515b571..1ba0d37 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4808,6 +4808,8 @@
<string name="accessibility_alarm_vibration_title">Alarm vibration</string>
<!-- Title for preference for configuring media vibrations (e.g. vibrations played together with animations, music, videos, etc). [CHAR LIMIT=NONE] -->
<string name="accessibility_media_vibration_title">Media vibration</string>
+ <!-- Toggle for keyboard vibration. [CHAR LIMIT=NONE]-->
+ <string name="accessibility_keyboard_vibration_title">Keyboard vibration</string>
<!-- Title for accessibility preference for configuring ring vibrations. [CHAR LIMIT=NONE] -->
<string name="accessibility_ring_vibration_title">Ring vibration</string>
<!-- Title for accessibility preference for configuring notification vibrations. -->
@@ -7339,6 +7341,9 @@
<!-- List of synonyms for the nfc tag apps control [CHAR LIMIT=NONE] -->
<string name="keywords_change_nfc_tag_apps_state">nfc, tag, reader</string>
+ <!-- List of synonyms for keyboard vibration settings search [CHAR LIMIT=NONE] -->
+ <string name="keywords_keyboard_vibration">keyboard, haptics, vibrate,</string>
+
<!-- Summary for sound settings, explaining a few important settings under it [CHAR LIMIT=NONE]-->
<string name="sound_dashboard_summary">Volume, vibration, Do Not Disturb</string>
diff --git a/res/xml/accessibility_vibration_intensity_settings.xml b/res/xml/accessibility_vibration_intensity_settings.xml
index d9505b5..13dc03b 100644
--- a/res/xml/accessibility_vibration_intensity_settings.xml
+++ b/res/xml/accessibility_vibration_intensity_settings.xml
@@ -77,6 +77,12 @@
app:keywords="@string/keywords_media_vibration"
app:controller="com.android.settings.accessibility.MediaVibrationIntensityPreferenceController" />
+ <SwitchPreference
+ android:key="vibration_intensity_preference_keyboard"
+ android:title="@string/accessibility_keyboard_vibration_title"
+ app:keywords="@string/keywords_keyboard_vibration"
+ app:controller="com.android.settings.accessibility.KeyboardVibrationTogglePreferenceController"/>
+
</PreferenceCategory>
</PreferenceScreen>
diff --git a/res/xml/accessibility_vibration_settings.xml b/res/xml/accessibility_vibration_settings.xml
index 5e2f923..0435cb4 100644
--- a/res/xml/accessibility_vibration_settings.xml
+++ b/res/xml/accessibility_vibration_settings.xml
@@ -77,6 +77,12 @@
app:keywords="@string/keywords_media_vibration"
app:controller="com.android.settings.accessibility.MediaVibrationTogglePreferenceController" />
+ <SwitchPreference
+ android:key="vibration_preference_keyboard"
+ android:title="@string/accessibility_keyboard_vibration_title"
+ app:keywords="@string/keywords_keyboard_vibration"
+ app:controller="com.android.settings.accessibility.KeyboardVibrationTogglePreferenceController"/>
+
</PreferenceCategory>
</PreferenceScreen>
diff --git a/res/xml/network_provider_sims_list.xml b/res/xml/network_provider_sims_list.xml
index b21341e..cc7589c 100644
--- a/res/xml/network_provider_sims_list.xml
+++ b/res/xml/network_provider_sims_list.xml
@@ -24,7 +24,7 @@
android:title="@string/summary_placeholder"
android:layout="@layout/preference_category_no_label"
android:order="20"
- settings:controller="com.android.settings.network.NetworkProviderSimsCategoryController"/>
+ settings:controller="com.android.settings.network.NetworkProviderSimListController"/>
<com.android.settingslib.RestrictedPreference
android:key="add_sim"
diff --git a/res/xml/reset_dashboard_fragment.xml b/res/xml/reset_dashboard_fragment.xml
index cd1c671..ab253ac 100644
--- a/res/xml/reset_dashboard_fragment.xml
+++ b/res/xml/reset_dashboard_fragment.xml
@@ -32,6 +32,7 @@
<!-- Bluetooth and WiFi reset -->
<com.android.settings.spa.preference.ComposePreference
android:key="network_reset_bluetooth_wifi_pref"
+ android:title="@string/reset_bluetooth_wifi_title"
settings:controller="com.android.settings.network.BluetoothWiFiResetPreferenceController" />
<!-- Reset app preferences -->
diff --git a/src/com/android/settings/DefaultRingtonePreference.java b/src/com/android/settings/DefaultRingtonePreference.java
index 9bf626c..4c65488 100644
--- a/src/com/android/settings/DefaultRingtonePreference.java
+++ b/src/com/android/settings/DefaultRingtonePreference.java
@@ -51,16 +51,9 @@
return;
}
- String mimeType = mUserContext.getContentResolver().getType(ringtoneUri);
- if (mimeType == null) {
+ if (!isValidRingtoneUri(ringtoneUri)) {
Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri
- + " ignored: failure to find mimeType (no access from this context?)");
- return;
- }
-
- if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
- Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri
- + " ignored: associated mimeType:" + mimeType + " is not an audio type");
+ + " ignored: invalid ringtone Uri");
return;
}
diff --git a/src/com/android/settings/RingtonePreference.java b/src/com/android/settings/RingtonePreference.java
index 8f9c618..de5b7c3 100644
--- a/src/com/android/settings/RingtonePreference.java
+++ b/src/com/android/settings/RingtonePreference.java
@@ -16,6 +16,8 @@
package com.android.settings;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@@ -23,9 +25,11 @@
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings.System;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.Log;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
@@ -239,4 +243,83 @@
return true;
}
+ public boolean isDefaultRingtone(Uri ringtoneUri) {
+ // null URIs are valid (None/silence)
+ return ringtoneUri == null || RingtoneManager.isDefault(ringtoneUri);
+ }
+
+ protected boolean isValidRingtoneUri(Uri ringtoneUri) {
+ if (isDefaultRingtone(ringtoneUri)) {
+ return true;
+ }
+
+ // Return early for android resource URIs
+ if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(ringtoneUri.getScheme())) {
+ return true;
+ }
+
+ String mimeType = mUserContext.getContentResolver().getType(ringtoneUri);
+ if (mimeType == null) {
+ Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri
+ + " failed: failure to find mimeType (no access from this context?)");
+ return false;
+ }
+
+ if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg")
+ || mimeType.equals("application/x-flac"))) {
+ Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri
+ + " failed: associated mimeType:" + mimeType + " is not an audio type");
+ return false;
+ }
+
+ // Validate userId from URIs: content://{userId}@...
+ final int userIdFromUri = ContentProvider.getUserIdFromUri(ringtoneUri, mUserId);
+ if (userIdFromUri != mUserId) {
+ final UserManager userManager = mUserContext.getSystemService(UserManager.class);
+
+ if (!userManager.isSameProfileGroup(mUserId, userIdFromUri)) {
+ Log.e(TAG,
+ "isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + userIdFromUri
+ + " and user " + mUserId + " are not in the same profile group");
+ return false;
+ }
+
+ final int parentUserId;
+ final int profileUserId;
+ if (userManager.isProfile()) {
+ profileUserId = mUserId;
+ parentUserId = userIdFromUri;
+ } else {
+ parentUserId = mUserId;
+ profileUserId = userIdFromUri;
+ }
+
+ final UserHandle parent = userManager.getProfileParent(UserHandle.of(profileUserId));
+ if (parent == null || parent.getIdentifier() != parentUserId) {
+ Log.e(TAG,
+ "isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + profileUserId
+ + " is not a profile of user " + parentUserId);
+ return false;
+ }
+
+ // Allow parent <-> managed profile sharing, unless restricted
+ if (userManager.hasUserRestrictionForUser(
+ UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, UserHandle.of(parentUserId))) {
+ Log.e(TAG,
+ "isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + parentUserId
+ + " has restriction: " + UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
+ return false;
+ }
+
+ if (!(userManager.isManagedProfile(profileUserId) || userManager.getUserProperties(
+ UserHandle.of(profileUserId)).isMediaSharedWithParent())) {
+ Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri
+ + " failed: user " + profileUserId + " is not a cloned or managed profile");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
}
diff --git a/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java b/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java
new file mode 100644
index 0000000..29d5928
--- /dev/null
+++ b/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java
@@ -0,0 +1,159 @@
+/*
+ * 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.accessibility;
+
+import static android.provider.Settings.System.KEYBOARD_VIBRATION_ENABLED;
+
+import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
+import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.VibrationAttributes;
+import android.os.Vibrator;
+import android.os.vibrator.Flags;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
+
+import com.android.settings.R;
+import com.android.settings.core.TogglePreferenceController;
+
+
+/**
+ * A preference controller to turn on/off keyboard vibration state with a single toggle.
+ */
+public class KeyboardVibrationTogglePreferenceController extends TogglePreferenceController
+ implements DefaultLifecycleObserver {
+
+ private static final String TAG = "KeyboardVibrateControl";
+
+ private static final Uri MAIN_VIBRATION_SWITCH_URI =
+ Settings.System.getUriFor(VibrationPreferenceConfig.MAIN_SWITCH_SETTING_KEY);
+
+ private final ContentObserver mContentObserver;
+
+ private final Vibrator mVibrator;
+
+ @Nullable
+ private SwitchPreference mPreference;
+
+ public KeyboardVibrationTogglePreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mVibrator = context.getSystemService(Vibrator.class);
+ mContentObserver = new ContentObserver(new Handler(/* async= */ true)) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (uri.equals(MAIN_VIBRATION_SWITCH_URI)) {
+ updateState(mPreference);
+ } else {
+ Log.w(TAG, "Unexpected uri change:" + uri);
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onStart(@NonNull LifecycleOwner owner) {
+ mContext.getContentResolver().registerContentObserver(MAIN_VIBRATION_SWITCH_URI,
+ /* notifyForDescendants= */ false, mContentObserver);
+ }
+
+ @Override
+ public void onStop(@NonNull LifecycleOwner owner) {
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public void updateState(@Nullable Preference preference) {
+ if (preference != null) {
+ super.updateState(preference);
+ preference.setEnabled(isPreferenceEnabled());
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (Flags.keyboardCategoryEnabled()
+ && mContext.getResources().getBoolean(R.bool.config_keyboard_vibration_supported)) {
+ return AVAILABLE;
+ }
+ return UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public boolean isChecked() {
+ // Always unchecked if the preference disabled
+ return isPreferenceEnabled() && isKeyboardVibrationSwitchEnabled();
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ final boolean success = updateKeyboardVibrationSetting(isChecked);
+ if (success && isChecked) {
+ // Play the preview vibration effect when the toggle is on.
+ final VibrationAttributes touchAttrs =
+ VibrationPreferenceConfig.createPreviewVibrationAttributes(
+ VibrationAttributes.USAGE_TOUCH);
+ final VibrationAttributes keyboardAttrs =
+ new VibrationAttributes.Builder(touchAttrs)
+ .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+ .build();
+ VibrationPreferenceConfig.playVibrationPreview(mVibrator, keyboardAttrs);
+ }
+ return true;
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ return R.string.menu_key_accessibility;
+ }
+
+ private boolean isPreferenceEnabled() {
+ return VibrationPreferenceConfig.isMainVibrationSwitchEnabled(
+ mContext.getContentResolver());
+ }
+
+ private boolean isKeyboardVibrationSwitchEnabled() {
+ return Settings.System.getInt(mContext.getContentResolver(), KEYBOARD_VIBRATION_ENABLED,
+ mVibrator.isDefaultKeyboardVibrationEnabled() ? ON : OFF) == ON;
+ }
+
+ private boolean updateKeyboardVibrationSetting(boolean enable) {
+ final boolean success = Settings.System.putInt(mContext.getContentResolver(),
+ KEYBOARD_VIBRATION_ENABLED, enable ? ON : OFF);
+ if (!success) {
+ Log.w(TAG, "Update settings database error!");
+ }
+ return success;
+ }
+}
diff --git a/src/com/android/settings/accessibility/VibrationPreferenceConfig.java b/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
index b4be528..c25c38e 100644
--- a/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
+++ b/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
@@ -68,8 +68,19 @@
/** Play a vibration effect with intensity just selected by the user. */
public static void playVibrationPreview(Vibrator vibrator,
@VibrationAttributes.Usage int vibrationUsage) {
- vibrator.vibrate(PREVIEW_VIBRATION_EFFECT,
- createPreviewVibrationAttributes(vibrationUsage));
+ playVibrationPreview(vibrator, createPreviewVibrationAttributes(vibrationUsage));
+ }
+
+ /**
+ * Play a vibration effect with intensity just selected by the user.
+ *
+ * @param vibrator The {@link Vibrator} used to play the vibration.
+ * @param vibrationAttributes The {@link VibrationAttributes} to indicate the
+ * vibration information.
+ */
+ public static void playVibrationPreview(Vibrator vibrator,
+ VibrationAttributes vibrationAttributes) {
+ vibrator.vibrate(PREVIEW_VIBRATION_EFFECT, vibrationAttributes);
}
public VibrationPreferenceConfig(Context context, String settingKey,
@@ -135,7 +146,7 @@
return mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT;
}
- private static VibrationAttributes createPreviewVibrationAttributes(
+ static VibrationAttributes createPreviewVibrationAttributes(
@VibrationAttributes.Usage int vibrationUsage) {
return new VibrationAttributes.Builder()
.setUsage(vibrationUsage)
diff --git a/src/com/android/settings/biometrics/BiometricEnrollBase.java b/src/com/android/settings/biometrics/BiometricEnrollBase.java
index c9c8cff..7df1fe1 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollBase.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollBase.java
@@ -67,6 +67,7 @@
public static final String EXTRA_FINISHED_ENROLL_FACE = "finished_enrolling_face";
public static final String EXTRA_FINISHED_ENROLL_FINGERPRINT = "finished_enrolling_fingerprint";
public static final String EXTRA_LAUNCHED_POSTURE_GUIDANCE = "launched_posture_guidance";
+ public static final String KEY_CALIBRATOR_UUID = "calibrator_uuid";
/**
* Used by the choose fingerprint wizard to indicate the wizard is
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
index be2a948..063d55d 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
@@ -68,10 +68,13 @@
import com.android.settings.biometrics.BiometricEnrollSidecar;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.biometrics.BiometricsEnrollEnrolling;
+import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.display.DisplayDensityUtils;
import com.airbnb.lottie.LottieAnimationView;
+import com.airbnb.lottie.LottieComposition;
import com.airbnb.lottie.LottieCompositionFactory;
import com.airbnb.lottie.LottieProperty;
import com.airbnb.lottie.model.KeyPath;
@@ -84,6 +87,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.function.Function;
/**
* Activity which handles the actual enrolling for fingerprint.
@@ -99,27 +103,22 @@
private static final int PROGRESS_BAR_MAX = 10000;
- private static final int STAGE_UNKNOWN = -1;
+ public static final int STAGE_UNKNOWN = -1;
private static final int STAGE_CENTER = 0;
private static final int STAGE_GUIDED = 1;
private static final int STAGE_FINGERTIP = 2;
private static final int STAGE_LEFT_EDGE = 3;
private static final int STAGE_RIGHT_EDGE = 4;
- @VisibleForTesting
- protected static final int SFPS_STAGE_NO_ANIMATION = 0;
+ public static final int SFPS_STAGE_NO_ANIMATION = 0;
- @VisibleForTesting
- protected static final int SFPS_STAGE_CENTER = 1;
+ public static final int SFPS_STAGE_CENTER = 1;
- @VisibleForTesting
- protected static final int SFPS_STAGE_FINGERTIP = 2;
+ public static final int SFPS_STAGE_FINGERTIP = 2;
- @VisibleForTesting
- protected static final int SFPS_STAGE_LEFT_EDGE = 3;
+ public static final int SFPS_STAGE_LEFT_EDGE = 3;
- @VisibleForTesting
- protected static final int SFPS_STAGE_RIGHT_EDGE = 4;
+ public static final int SFPS_STAGE_RIGHT_EDGE = 4;
@IntDef({STAGE_UNKNOWN, STAGE_CENTER, STAGE_GUIDED, STAGE_FINGERTIP, STAGE_LEFT_EDGE,
STAGE_RIGHT_EDGE})
@@ -196,6 +195,9 @@
private OrientationEventListener mOrientationEventListener;
private int mPreviousRotation = 0;
+ @NonNull
+ private SfpsEnrollmentFeature mSfpsEnrollmentFeature = new EmptySfpsEnrollmentFeature();
+
@VisibleForTesting
protected boolean shouldShowLottie() {
DisplayDensityUtils displayDensity = new DisplayDensityUtils(getApplicationContext());
@@ -244,6 +246,8 @@
setContentView(layout);
setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
} else if (mCanAssumeSfps) {
+ mSfpsEnrollmentFeature = FeatureFactory.getFeatureFactory()
+ .getFingerprintFeatureProvider().getSfpsEnrollmentFeature();
setContentView(R.layout.sfps_enroll_enrolling);
setHelpAnimation();
} else {
@@ -599,7 +603,8 @@
}
switch (getCurrentSfpsStage()) {
case SFPS_STAGE_NO_ANIMATION:
- setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
+ setHeaderText(mSfpsEnrollmentFeature
+ .getFeaturedStageHeaderResource(SFPS_STAGE_NO_ANIMATION));
if (!mHaveShownSfpsNoAnimationLottie && mIllustrationLottie != null) {
mHaveShownSfpsNoAnimationLottie = true;
mIllustrationLottie.setContentDescription(
@@ -608,39 +613,48 @@
0
)
);
- configureEnrollmentStage(R.raw.sfps_lottie_no_animation);
+ configureEnrollmentStage(mSfpsEnrollmentFeature
+ .getSfpsEnrollLottiePerStage(SFPS_STAGE_NO_ANIMATION));
}
break;
case SFPS_STAGE_CENTER:
- setHeaderText(R.string.security_settings_sfps_enroll_finger_center_title);
+ setHeaderText(mSfpsEnrollmentFeature
+ .getFeaturedStageHeaderResource(SFPS_STAGE_CENTER));
if (!mHaveShownSfpsCenterLottie && mIllustrationLottie != null) {
mHaveShownSfpsCenterLottie = true;
- configureEnrollmentStage(R.raw.sfps_lottie_pad_center);
+ configureEnrollmentStage(mSfpsEnrollmentFeature
+ .getSfpsEnrollLottiePerStage(SFPS_STAGE_CENTER));
}
break;
case SFPS_STAGE_FINGERTIP:
- setHeaderText(R.string.security_settings_sfps_enroll_fingertip_title);
+ setHeaderText(mSfpsEnrollmentFeature
+ .getFeaturedStageHeaderResource(SFPS_STAGE_FINGERTIP));
if (!mHaveShownSfpsTipLottie && mIllustrationLottie != null) {
mHaveShownSfpsTipLottie = true;
- configureEnrollmentStage(R.raw.sfps_lottie_tip);
+ configureEnrollmentStage(mSfpsEnrollmentFeature
+ .getSfpsEnrollLottiePerStage(SFPS_STAGE_FINGERTIP));
}
break;
case SFPS_STAGE_LEFT_EDGE:
- setHeaderText(R.string.security_settings_sfps_enroll_left_edge_title);
+ setHeaderText(mSfpsEnrollmentFeature
+ .getFeaturedStageHeaderResource(SFPS_STAGE_LEFT_EDGE));
if (!mHaveShownSfpsLeftEdgeLottie && mIllustrationLottie != null) {
mHaveShownSfpsLeftEdgeLottie = true;
- configureEnrollmentStage(R.raw.sfps_lottie_left_edge);
+ configureEnrollmentStage(mSfpsEnrollmentFeature
+ .getSfpsEnrollLottiePerStage(SFPS_STAGE_LEFT_EDGE));
}
break;
case SFPS_STAGE_RIGHT_EDGE:
- setHeaderText(R.string.security_settings_sfps_enroll_right_edge_title);
+ setHeaderText(mSfpsEnrollmentFeature
+ .getFeaturedStageHeaderResource(SFPS_STAGE_RIGHT_EDGE));
if (!mHaveShownSfpsRightEdgeLottie && mIllustrationLottie != null) {
mHaveShownSfpsRightEdgeLottie = true;
- configureEnrollmentStage(R.raw.sfps_lottie_right_edge);
+ configureEnrollmentStage(mSfpsEnrollmentFeature
+ .getSfpsEnrollLottiePerStage(SFPS_STAGE_RIGHT_EDGE));
}
break;
@@ -665,11 +679,16 @@
setDescriptionText("");
}
LottieCompositionFactory.fromRawRes(this, lottie)
- .addListener((c) -> {
- mIllustrationLottie.setComposition(c);
- mIllustrationLottie.setVisibility(View.VISIBLE);
- mIllustrationLottie.playAnimation();
- });
+ .addListener((c) -> onLottieComposition(mIllustrationLottie, c));
+ }
+
+ private void onLottieComposition(LottieAnimationView view, LottieComposition composition) {
+ if (view == null || composition == null) {
+ return;
+ }
+ view.setComposition(composition);
+ view.setVisibility(View.VISIBLE);
+ view.playAnimation();
}
@EnrollStage
@@ -699,17 +718,8 @@
}
final int progressSteps = mSidecar.getEnrollmentSteps() - mSidecar.getEnrollmentRemaining();
- if (progressSteps < getStageThresholdSteps(0)) {
- return SFPS_STAGE_NO_ANIMATION;
- } else if (progressSteps < getStageThresholdSteps(1)) {
- return SFPS_STAGE_CENTER;
- } else if (progressSteps < getStageThresholdSteps(2)) {
- return SFPS_STAGE_FINGERTIP;
- } else if (progressSteps < getStageThresholdSteps(3)) {
- return SFPS_STAGE_LEFT_EDGE;
- } else {
- return SFPS_STAGE_RIGHT_EDGE;
- }
+ return mSfpsEnrollmentFeature
+ .getCurrentSfpsEnrollStage(progressSteps, this::getStageThresholdSteps);
}
private boolean isStageHalfCompleted() {
@@ -740,22 +750,31 @@
Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet");
return 1;
}
- return Math.round(mSidecar.getEnrollmentSteps()
- * mFingerprintManager.getEnrollStageThreshold(index));
+ final float threshold = mCanAssumeSfps
+ ? mSfpsEnrollmentFeature.getEnrollStageThreshold(this, index)
+ : mFingerprintManager.getEnrollStageThreshold(index);
+ return Math.round(mSidecar.getEnrollmentSteps() * threshold);
}
@Override
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
- if (!TextUtils.isEmpty(helpString)) {
+ final CharSequence featuredString = mCanAssumeSfps
+ ? mSfpsEnrollmentFeature.getFeaturedVendorString(this, helpMsgId, helpString)
+ : helpString;
+
+ if (!TextUtils.isEmpty(featuredString)) {
if (!(mCanAssumeUdfps || mCanAssumeSfps)) {
mErrorText.removeCallbacks(mTouchAgainRunnable);
}
- showError(helpString);
+ showError(featuredString);
if (mUdfpsEnrollHelper != null) mUdfpsEnrollHelper.onEnrollmentHelp();
}
dismissTouchDialogIfSfps();
+ if (mCanAssumeSfps) {
+ mSfpsEnrollmentFeature.handleOnEnrollmentHelp(helpMsgId, featuredString, () -> this);
+ }
}
@Override
@@ -1170,4 +1189,28 @@
return SettingsEnums.DIALOG_FINGERPRINT_ICON_TOUCH;
}
}
-}
\ No newline at end of file
+
+ private static class EmptySfpsEnrollmentFeature implements SfpsEnrollmentFeature {
+ private final String exceptionStr = "Assume sfps but no SfpsEnrollmentFeature impl.";
+
+ @Override
+ public int getCurrentSfpsEnrollStage(int progressSteps, Function<Integer, Integer> mapper) {
+ throw new IllegalStateException(exceptionStr);
+ }
+
+ @Override
+ public int getFeaturedStageHeaderResource(int stage) {
+ throw new IllegalStateException(exceptionStr);
+ }
+
+ @Override
+ public int getSfpsEnrollLottiePerStage(int stage) {
+ throw new IllegalStateException(exceptionStr);
+ }
+
+ @Override
+ public float getEnrollStageThreshold(@NonNull Context context, int index) {
+ throw new IllegalStateException(exceptionStr);
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
index c207ec9..276845c 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
@@ -16,6 +16,8 @@
package com.android.settings.biometrics.fingerprint;
+import static android.text.Layout.HYPHENATION_FREQUENCY_NORMAL;
+
import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.content.res.Configuration;
@@ -23,21 +25,27 @@
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.util.Log;
import android.view.OrientationEventListener;
import android.view.Surface;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.lifecycle.Observer;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricEnrollSidecar;
import com.android.settings.biometrics.BiometricUtils;
+import com.android.settings.biometrics.fingerprint.UdfpsEnrollCalibrator.Result;
+import com.android.settings.biometrics.fingerprint.UdfpsEnrollCalibrator.Status;
+import com.android.settings.flags.Flags;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.widget.LottieColorUtils;
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
@@ -48,6 +56,7 @@
import com.google.android.setupcompat.template.FooterButton;
import java.util.List;
+import java.util.UUID;
/**
* Activity explaining the fingerprint sensor location for fingerprint enrollment.
@@ -74,6 +83,10 @@
private ScreenSizeFoldProvider mScreenSizeFoldProvider;
private boolean mIsFolded;
private boolean mIsReverseDefaultRotation;
+ @Nullable
+ private UdfpsEnrollCalibrator mCalibrator;
+ @Nullable
+ private Observer<Status> mCalibratorStatusObserver;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -98,20 +111,13 @@
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build()
);
+ getLayout().getHeaderTextView().setHyphenationFrequency(HYPHENATION_FREQUENCY_NORMAL);
listenOrientationEvent();
if (mCanAssumeUdfps) {
setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title);
setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message);
- mFooterBarMixin.setPrimaryButton(
- new FooterButton.Builder(this)
- .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
- .setListener(this::onStartButtonClick)
- .setButtonType(FooterButton.ButtonType.NEXT)
- .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
- .build()
- );
mIllustrationLottie = findViewById(R.id.illustration_lottie);
AccessibilityManager am = getSystemService(AccessibilityManager.class);
@@ -164,12 +170,20 @@
mAnimation = null;
if (mCanAssumeUdfps) {
- mIllustrationLottie.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onStartButtonClick(v);
+ if (Flags.udfpsEnrollCalibration()) {
+ mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider()
+ .getUdfpsEnrollCalibrator(
+ (savedInstanceState != null)
+ ? savedInstanceState.getParcelable(KEY_CALIBRATOR_UUID, UUID.class)
+ : getIntent().getSerializableExtra(KEY_CALIBRATOR_UUID, UUID.class)
+ );
+ if (mCalibrator == null
+ || mCalibrator.getStatusLiveData().getValue() == Status.FINISHED) {
+ enableUdfpsLottieAndNextButton();
}
- });
+ } else {
+ enableUdfpsLottieAndNextButton();
+ }
} else if (!mCanAssumeSfps) {
View animationView = findViewById(R.id.fingerprint_sensor_location_animation);
if (animationView instanceof FingerprintFindSensorAnimation) {
@@ -178,6 +192,20 @@
}
}
+ private void enableUdfpsLottieAndNextButton() {
+ mFooterBarMixin.setPrimaryButton(
+ new FooterButton.Builder(this)
+ .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
+ .setListener(this::onStartButtonClick)
+ .setButtonType(FooterButton.ButtonType.NEXT)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
+ .build()
+ );
+ if (mIllustrationLottie != null) {
+ mIllustrationLottie.setOnClickListener(this::onStartButtonClick);
+ }
+ }
+
private int getRotationFromDefault(int rotation) {
if (mIsReverseDefaultRotation) {
return (rotation + 1) % 4;
@@ -255,6 +283,11 @@
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(SAVED_STATE_IS_NEXT_CLICKED, mNextClicked);
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ outState.putSerializable(KEY_CALIBRATOR_UUID, mCalibrator.getUuid());
+ }
+ }
}
@Override
@@ -284,6 +317,39 @@
if (mAnimation != null) {
mAnimation.startAnimation();
}
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ final Status current = mCalibrator.getStatusLiveData().getValue();
+ if (current == Status.PROCESSING) {
+ if (mCalibratorStatusObserver == null) {
+ mCalibratorStatusObserver = status -> {
+ if (status == Status.GOT_RESULT) {
+ onGotCalibrationResult();
+ }
+ };
+ }
+ mCalibrator.getStatusLiveData().observe(this, mCalibratorStatusObserver);
+ } else if (current == Status.GOT_RESULT) {
+ onGotCalibrationResult();
+ }
+ }
+ }
+ }
+
+ private void onGotCalibrationResult() {
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ mCalibrator.setFinished();
+ if (mCalibrator.getResult() == Result.NEED_CALIBRATION) {
+ UdfpsEnrollCalibrationDialog.newInstance(
+ mCalibrator.getCalibrationDialogTitleTextId(),
+ mCalibrator.getCalibrationDialogMessageTextId(),
+ mCalibrator.getCalibrationDialogDismissButtonTextId()
+ ).show(getSupportFragmentManager(), "findsensor-calibration-dialog");
+ }
+ }
+ new Handler(Looper.getMainLooper()).post(this::enableUdfpsLottieAndNextButton);
+ }
}
private void stopLookingForFingerprint() {
@@ -341,6 +407,12 @@
if (mAnimation != null) {
mAnimation.pauseAnimation();
}
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null && mCalibratorStatusObserver != null) {
+ mCalibrator.getStatusLiveData().removeObserver(mCalibratorStatusObserver);
+ mCalibratorStatusObserver = null;
+ }
+ }
}
@Override
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
index df23a5c..dc3c65e 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
@@ -45,6 +45,8 @@
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.biometrics.GatekeeperPasswordProvider;
import com.android.settings.biometrics.MultiBiometricEnrollHelper;
+import com.android.settings.flags.Flags;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -55,6 +57,7 @@
import com.google.android.setupdesign.util.DeviceHelper;
import java.util.List;
+import java.util.UUID;
public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@@ -67,6 +70,8 @@
private DevicePolicyManager mDevicePolicyManager;
private boolean mCanAssumeUdfps;
+ @Nullable
+ private UdfpsEnrollCalibrator mCalibrator;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -85,6 +90,16 @@
mDevicePolicyManager = getSystemService(DevicePolicyManager.class);
+ if (Flags.udfpsEnrollCalibration()) {
+ mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider()
+ .getUdfpsEnrollCalibrator(
+ (savedInstanceState != null)
+ ? savedInstanceState.getParcelable(
+ KEY_CALIBRATOR_UUID, UUID.class)
+ : null
+ );
+ }
+
final ImageView iconFingerprint = findViewById(R.id.icon_fingerprint);
final ImageView iconDeviceLocked = findViewById(R.id.icon_device_locked);
final ImageView iconTrashCan = findViewById(R.id.icon_trash_can);
@@ -156,6 +171,16 @@
}
@Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ outState.putSerializable(KEY_CALIBRATOR_UUID, mCalibrator.getUuid());
+ }
+ }
+ }
+
+ @Override
protected void initViews() {
setDescriptionText(getString(
R.string.security_settings_fingerprint_enroll_introduction_v3_message,
@@ -364,6 +389,11 @@
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
BiometricUtils.getGatekeeperPasswordHandle(getIntent()));
}
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ intent.putExtra(KEY_CALIBRATOR_UUID, mCalibrator.getUuid());
+ }
+ }
return intent;
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
new file mode 100644
index 0000000..5a2bf8b
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
@@ -0,0 +1,39 @@
+/*
+ * 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.biometrics.fingerprint;
+
+import androidx.annotation.Nullable;
+
+import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature;
+
+import java.util.UUID;
+
+public interface FingerprintFeatureProvider {
+ /**
+ * Gets the feature implementation of SFPS enrollment.
+ * @return the feature implementation
+ */
+ SfpsEnrollmentFeature getSfpsEnrollmentFeature();
+
+ /**
+ * Gets calibrator to calibrate the FPS before enrolling udfps
+ * @param uuid unique id for passed between different activities
+ * @return udfps calibrator
+ */
+ @Nullable
+ UdfpsEnrollCalibrator getUdfpsEnrollCalibrator(@Nullable UUID uuid);
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java
new file mode 100644
index 0000000..1baabc6
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java
@@ -0,0 +1,44 @@
+/*
+ * 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.biometrics.fingerprint;
+
+import androidx.annotation.Nullable;
+
+import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature;
+import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeatureImpl;
+
+import java.util.UUID;
+
+public class FingerprintFeatureProviderImpl implements FingerprintFeatureProvider {
+
+ @Nullable
+ private SfpsEnrollmentFeature mSfpsEnrollmentFeatureImpl = null;
+
+ @Override
+ public SfpsEnrollmentFeature getSfpsEnrollmentFeature() {
+ if (mSfpsEnrollmentFeatureImpl == null) {
+ mSfpsEnrollmentFeatureImpl = new SfpsEnrollmentFeatureImpl();
+ }
+ return mSfpsEnrollmentFeatureImpl;
+ }
+
+ @Nullable
+ @Override
+ public UdfpsEnrollCalibrator getUdfpsEnrollCalibrator(@Nullable UUID uuid) {
+ return null;
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrationDialog.kt b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrationDialog.kt
new file mode 100644
index 0000000..892996a
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrationDialog.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.biometrics.fingerprint
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import com.android.settings.R
+
+class UdfpsEnrollCalibrationDialog : DialogFragment() {
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
+ AlertDialog.Builder(requireActivity(), R.style.Theme_AlertDialog)
+ .setTitle(arguments!!.getInt(KEY_TITLE_TEXT_ID))
+ .setMessage(arguments!!.getInt(KEY_MESSAGE_TEXT_ID))
+ .setPositiveButton(arguments!!.getInt(KEY_DISMISS_BUTTON_TEXT_ID)) {
+ dialog: DialogInterface?, _: Int -> dialog?.dismiss()
+ }
+ .create().also {
+ isCancelable = false
+ }
+
+ companion object {
+
+ private const val KEY_TITLE_TEXT_ID = "title_text_id"
+ private const val KEY_MESSAGE_TEXT_ID = "message_text_id"
+ private const val KEY_DISMISS_BUTTON_TEXT_ID = "dismiss_button_text_id"
+
+ @JvmStatic
+ fun newInstance(
+ @StringRes titleTextId: Int,
+ @StringRes messageTextId: Int,
+ @StringRes dismissButtonTextId: Int
+ ) = UdfpsEnrollCalibrationDialog().apply {
+ arguments = Bundle().apply {
+ putInt(KEY_TITLE_TEXT_ID, titleTextId)
+ putInt(KEY_MESSAGE_TEXT_ID, messageTextId)
+ putInt(KEY_DISMISS_BUTTON_TEXT_ID, dismissButtonTextId)
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
new file mode 100644
index 0000000..c0626d3
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
@@ -0,0 +1,36 @@
+package com.android.settings.biometrics.fingerprint
+
+import androidx.annotation.StringRes
+import androidx.lifecycle.LiveData
+import java.util.UUID
+
+interface UdfpsEnrollCalibrator {
+
+ enum class Status {
+ PROCESSING,
+ GOT_RESULT,
+ FINISHED,
+ }
+
+ enum class Result {
+ NEED_CALIBRATION,
+ NO_NEED_CALIBRATION,
+ }
+
+ val uuid: UUID
+
+ val statusLiveData: LiveData<Status>
+
+ val result: Result?
+
+ fun setFinished()
+
+ @get:StringRes
+ val calibrationDialogTitleTextId: Int
+
+ @get:StringRes
+ val calibrationDialogMessageTextId: Int
+
+ @get:StringRes
+ val calibrationDialogDismissButtonTextId: Int
+}
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
new file mode 100644
index 0000000..a1a18e5
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
@@ -0,0 +1,79 @@
+/*
+ * 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.biometrics.fingerprint.feature;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public interface SfpsEnrollmentFeature {
+
+ /**
+ * Gets current SFPS enrollment stage.
+ * @param progressSteps current step of enrollment
+ * @param mapper a mapper to map each stage to its threshold
+ * @return current enrollment stage
+ */
+ int getCurrentSfpsEnrollStage(int progressSteps, Function<Integer, Integer> mapper);
+
+ /**
+ * Gets the vendor string by feature.
+ * @param context Context
+ * @param id An integer identifying the error message
+ * @param msg A human-readable string that can be shown in UI
+ * @return A human-readable string of specific feature
+ */
+ default CharSequence getFeaturedVendorString(Context context, int id, CharSequence msg) {
+ return msg;
+ }
+
+ /**
+ * Gets the stage header string by feature.
+ * @param stage the specific stage
+ * @return the resource id of the header text of the specific stage
+ */
+ int getFeaturedStageHeaderResource(int stage);
+
+ /**
+ * Gets the enrollment lottie resource id per stage
+ * @param stage current enrollment stage
+ * @return enrollment lottie resource id
+ */
+ int getSfpsEnrollLottiePerStage(int stage);
+
+ /**
+ * Handles extra stuffs on receiving enrollment help.
+ * @param helpMsgId help message id
+ * @param helpString help message
+ * @param enrollingSupplier supplier of enrolling context
+ */
+ default void handleOnEnrollmentHelp(int helpMsgId, CharSequence helpString,
+ Supplier<FingerprintEnrollEnrolling> enrollingSupplier) {}
+
+ /**
+ * Gets the fingerprint enrollment threshold.
+ * @param context context
+ * @param index the enrollment stage index
+ * @return threshold
+ */
+ float getEnrollStageThreshold(@NonNull Context context, int index);
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
new file mode 100644
index 0000000..5a97537
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
@@ -0,0 +1,91 @@
+/*
+ * 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.biometrics.fingerprint.feature;
+
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_CENTER;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_FINGERTIP;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_LEFT_EDGE;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_NO_ANIMATION;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_RIGHT_EDGE;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.STAGE_UNKNOWN;
+
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+
+import java.util.function.Function;
+
+public class SfpsEnrollmentFeatureImpl implements SfpsEnrollmentFeature {
+
+ @Nullable
+ private FingerprintManager mFingerprintManager = null;
+
+ @Override
+ public int getCurrentSfpsEnrollStage(int progressSteps, Function<Integer, Integer> mapper) {
+ if (mapper == null) {
+ return STAGE_UNKNOWN;
+ }
+ if (progressSteps < mapper.apply(0)) {
+ return SFPS_STAGE_NO_ANIMATION;
+ } else if (progressSteps < mapper.apply(1)) {
+ return SFPS_STAGE_CENTER;
+ } else if (progressSteps < mapper.apply(2)) {
+ return SFPS_STAGE_FINGERTIP;
+ } else if (progressSteps < mapper.apply(3)) {
+ return SFPS_STAGE_LEFT_EDGE;
+ } else {
+ return SFPS_STAGE_RIGHT_EDGE;
+ }
+ }
+
+ @Override
+ public int getFeaturedStageHeaderResource(int stage) {
+ return switch (stage) {
+ case SFPS_STAGE_NO_ANIMATION
+ -> R.string.security_settings_fingerprint_enroll_repeat_title;
+ case SFPS_STAGE_CENTER -> R.string.security_settings_sfps_enroll_finger_center_title;
+ case SFPS_STAGE_FINGERTIP -> R.string.security_settings_sfps_enroll_fingertip_title;
+ case SFPS_STAGE_LEFT_EDGE -> R.string.security_settings_sfps_enroll_left_edge_title;
+ case SFPS_STAGE_RIGHT_EDGE -> R.string.security_settings_sfps_enroll_right_edge_title;
+ default -> throw new IllegalArgumentException("Invalid stage: " + stage);
+ };
+ }
+
+ @Override
+ public int getSfpsEnrollLottiePerStage(int stage) {
+ return switch (stage) {
+ case SFPS_STAGE_NO_ANIMATION -> R.raw.sfps_lottie_no_animation;
+ case SFPS_STAGE_CENTER -> R.raw.sfps_lottie_pad_center;
+ case SFPS_STAGE_FINGERTIP -> R.raw.sfps_lottie_tip;
+ case SFPS_STAGE_LEFT_EDGE -> R.raw.sfps_lottie_left_edge;
+ case SFPS_STAGE_RIGHT_EDGE -> R.raw.sfps_lottie_right_edge;
+ default -> throw new IllegalArgumentException("Invalid stage: " + stage);
+ };
+ }
+
+ @Override
+ public float getEnrollStageThreshold(@NonNull Context context, int index) {
+ if (mFingerprintManager == null) {
+ mFingerprintManager = context.getSystemService(FingerprintManager.class);
+ }
+ return mFingerprintManager.getEnrollStageThreshold(index);
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
index 4024692..898b158 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
@@ -186,9 +186,6 @@
return view
}
- /**
- * TODO (b/305269201): This link isn't displaying for screenshot tests.
- */
private fun setFooterLink(view: View) {
val footerLink: TextView = view.requireViewById(R.id.footer_learn_more)
footerLink.movementMethod = LinkMovementMethod.getInstance()
diff --git a/src/com/android/settings/connecteddevice/fastpair/FastPairDeviceGroupController.java b/src/com/android/settings/connecteddevice/fastpair/FastPairDeviceGroupController.java
index ac117f1..8918f01 100644
--- a/src/com/android/settings/connecteddevice/fastpair/FastPairDeviceGroupController.java
+++ b/src/com/android/settings/connecteddevice/fastpair/FastPairDeviceGroupController.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -44,7 +45,10 @@
public class FastPairDeviceGroupController extends BasePreferenceController
implements PreferenceControllerMixin, DefaultLifecycleObserver, DevicePreferenceCallback {
+ private static final String TAG = "FastPairDeviceGroupCtr";
+
private static final String KEY = "fast_pair_device_list";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@VisibleForTesting PreferenceGroup mPreferenceGroup;
private final FastPairDeviceUpdater mFastPairDeviceUpdater;
@@ -68,6 +72,7 @@
mFastPairDeviceUpdater =
fastPairFeatureProvider.getFastPairDeviceUpdater(context, this);
} else {
+ Log.d(TAG, "Flag disabled. Ignored.");
mFastPairDeviceUpdater = null;
}
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -78,6 +83,10 @@
public void onStart(@NonNull LifecycleOwner owner) {
if (mFastPairDeviceUpdater != null) {
mFastPairDeviceUpdater.registerCallback();
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Callback register: Fast Pair device updater is null. Ignore.");
+ }
}
mContext.registerReceiver(mReceiver, mIntentFilter, Context.RECEIVER_EXPORTED_UNAUDITED);
}
@@ -86,6 +95,10 @@
public void onStop(@NonNull LifecycleOwner owner) {
if (mFastPairDeviceUpdater != null) {
mFastPairDeviceUpdater.unregisterCallback();
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Callback unregister: Fast Pair device updater is null. Ignore.");
+ }
}
mContext.unregisterReceiver(mReceiver);
}
@@ -117,14 +130,24 @@
@Override
public void onDeviceAdded(Preference preference) {
- if (preference == null) return;
+ if (preference == null) {
+ if (DEBUG) {
+ Log.d(TAG, "onDeviceAdded receives null preference. Ignore.");
+ }
+ return;
+ }
mPreferenceGroup.addPreference(preference);
updatePreferenceVisibility();
}
@Override
public void onDeviceRemoved(Preference preference) {
- if (preference == null) return;
+ if (preference == null) {
+ if (DEBUG) {
+ Log.d(TAG, "onDeviceRemoved receives null preference. Ignore.");
+ }
+ return;
+ }
mPreferenceGroup.removePreference(preference);
updatePreferenceVisibility();
}
diff --git a/src/com/android/settings/connecteddevice/fastpair/FastPairDevicePreferenceController.java b/src/com/android/settings/connecteddevice/fastpair/FastPairDevicePreferenceController.java
index 6a5b6b9..76c9d00 100644
--- a/src/com/android/settings/connecteddevice/fastpair/FastPairDevicePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/fastpair/FastPairDevicePreferenceController.java
@@ -81,6 +81,7 @@
mFastPairDeviceUpdater =
fastPairFeatureProvider.getFastPairDeviceUpdater(context, this);
} else {
+ Log.d(TAG, "Flag disabled. Ignore.");
mFastPairDeviceUpdater = null;
}
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
@@ -91,6 +92,10 @@
public void onStart(@NonNull LifecycleOwner owner) {
if (mFastPairDeviceUpdater != null) {
mFastPairDeviceUpdater.registerCallback();
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Callback register: Fast Pair device updater is null. Ignore.");
+ }
}
mContext.registerReceiver(mReceiver, mIntentFilter, Context.RECEIVER_EXPORTED_UNAUDITED);
}
@@ -99,6 +104,10 @@
public void onStop(@NonNull LifecycleOwner owner) {
if (mFastPairDeviceUpdater != null) {
mFastPairDeviceUpdater.unregisterCallback();
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Callback unregister: Fast Pair device updater is null. Ignore.");
+ }
}
mContext.unregisterReceiver(mReceiver);
}
diff --git a/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java b/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java
index ce17418..a6dd732 100644
--- a/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java
+++ b/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java
@@ -30,6 +30,7 @@
import android.view.View;
import android.widget.Toast;
+import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockSettingsHelper;
@@ -97,10 +98,19 @@
@Override
public void onNavigateNext(View view) {
// Ensure that all users are unlocked so that we can move their data
+ final LockPatternUtils lpu = new LockPatternUtils(this);
if (StorageManager.isFileEncrypted()) {
for (UserInfo user : getSystemService(UserManager.class).getUsers()) {
- if (!StorageManager.isUserKeyUnlocked(user.id)) {
- Log.d(TAG, "User " + user.id + " is currently locked; requesting unlock");
+ if (StorageManager.isUserKeyUnlocked(user.id)) {
+ continue;
+ }
+ if (!lpu.isSecure(user.id)) {
+ Log.d(TAG, "Unsecured user " + user.id + " is currently locked; attempting "
+ + "automatic unlock");
+ lpu.unlockUserKeyIfUnsecured(user.id);
+ } else {
+ Log.d(TAG, "Secured user " + user.id + " is currently locked; requesting "
+ + "manual unlock");
final CharSequence description = TextUtils.expandTemplate(
getText(R.string.storage_wizard_move_unlock), user.name);
final ChooseLockSettingsHelper.Builder builder =
diff --git a/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java b/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java
index da96104..bf16ab0 100644
--- a/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java
+++ b/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java
@@ -33,6 +33,7 @@
import android.view.View;
import com.android.internal.util.Preconditions;
+import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.password.ChooseLockSettingsHelper;
@@ -79,10 +80,19 @@
@Override
public void onNavigateNext(View view) {
// Ensure that all users are unlocked so that we can move their data
+ final LockPatternUtils lpu = new LockPatternUtils(this);
if (StorageManager.isFileEncrypted()) {
for (UserInfo user : getSystemService(UserManager.class).getUsers()) {
- if (!StorageManager.isUserKeyUnlocked(user.id)) {
- Log.d(TAG, "User " + user.id + " is currently locked; requesting unlock");
+ if (StorageManager.isUserKeyUnlocked(user.id)) {
+ continue;
+ }
+ if (!lpu.isSecure(user.id)) {
+ Log.d(TAG, "Unsecured user " + user.id + " is currently locked; attempting "
+ + "automatic unlock");
+ lpu.unlockUserKeyIfUnsecured(user.id);
+ } else {
+ Log.d(TAG, "Secured user " + user.id + " is currently locked; requesting "
+ + "manual unlock");
final CharSequence description = TextUtils.expandTemplate(
getText(R.string.storage_wizard_move_unlock), user.name);
final ChooseLockSettingsHelper.Builder builder =
diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java
index 171c76b..3b958ae 100644
--- a/src/com/android/settings/fuelgauge/BatteryUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryUtils.java
@@ -74,6 +74,7 @@
* Utils for battery operation
*/
public class BatteryUtils {
+ public static final int UID_ZERO = 0;
public static final int UID_NULL = -1;
public static final int SDK_NULL = -1;
/** Special UID value for data usage by removed apps. */
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
index 971ada9..9d7b629 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
@@ -283,7 +283,9 @@
/** Whether the current BatteryDiffEntry is uninstalled app or not. */
public boolean isUninstalledEntry() {
final String packageName = getPackageName();
- if (TextUtils.isEmpty(packageName) || isSystemEntry()) {
+ if (TextUtils.isEmpty(packageName) || isSystemEntry()
+ // Some special package UIDs could be 0. Those packages are not installed by users.
+ || mUid == BatteryUtils.UID_ZERO) {
return false;
}
diff --git a/src/com/android/settings/network/MobileNetworkListFragment.java b/src/com/android/settings/network/MobileNetworkListFragment.java
index d7d241a..3de05af 100644
--- a/src/com/android/settings/network/MobileNetworkListFragment.java
+++ b/src/com/android/settings/network/MobileNetworkListFragment.java
@@ -19,26 +19,19 @@
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.UserManager;
-import android.provider.SearchIndexableResource;
-import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.network.telephony.MobileNetworkUtils;
import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
-import java.util.ArrayList;
-import java.util.List;
-
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class MobileNetworkListFragment extends DashboardFragment {
private static final String LOG_TAG = "NetworkListFragment";
- static final String KEY_PREFERENCE_CATEGORY_SIM = "provider_model_sim_category";
private static final String KEY_ADD_SIM = "add_sim";
@Override
@@ -68,34 +61,8 @@
return SettingsEnums.MOBILE_NETWORK_LIST;
}
- @Override
- protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
- final List<AbstractPreferenceController> controllers = new ArrayList<>();
- if (!SubscriptionUtil.isSimHardwareVisible(getContext())) {
- finish();
- return controllers;
- }
-
- NetworkProviderSimsCategoryController simCategoryPrefCtrl =
- new NetworkProviderSimsCategoryController(context, KEY_PREFERENCE_CATEGORY_SIM,
- getSettingsLifecycle(), this);
- controllers.add(simCategoryPrefCtrl);
-
- return controllers;
- }
-
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider() {
-
- @Override
- public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
- boolean enabled) {
- final ArrayList<SearchIndexableResource> result = new ArrayList<>();
- final SearchIndexableResource sir = new SearchIndexableResource(context);
- sir.xmlResId = R.xml.network_provider_sims_list;
- result.add(sir);
- return result;
- }
+ new BaseSearchIndexProvider(R.xml.network_provider_sims_list) {
@Override
protected boolean isPageSearchEnabled(Context context) {
diff --git a/src/com/android/settings/network/NetworkProviderSimListController.java b/src/com/android/settings/network/NetworkProviderSimListController.java
index 89cb73c..02b1980 100644
--- a/src/com/android/settings/network/NetworkProviderSimListController.java
+++ b/src/com/android/settings/network/NetworkProviderSimListController.java
@@ -16,69 +16,60 @@
package com.android.settings.network;
-import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
-
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.telephony.SubscriptionManager;
import android.util.ArrayMap;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
import com.android.settings.network.telephony.MobileNetworkUtils;
import com.android.settingslib.RestrictedPreference;
-import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-public class NetworkProviderSimListController extends AbstractPreferenceController implements
- LifecycleObserver, MobileNetworkRepository.MobileNetworkCallback,
+public class NetworkProviderSimListController extends BasePreferenceController implements
+ DefaultLifecycleObserver, MobileNetworkRepository.MobileNetworkCallback,
DefaultSubscriptionReceiver.DefaultSubscriptionListener {
- private static final String TAG = "NetworkProviderSimListCtrl";
- private static final String KEY_PREFERENCE_CATEGORY_SIM = "provider_model_sim_category";
- private static final String KEY_PREFERENCE_SIM = "provider_model_sim_list";
- private SubscriptionManager mSubscriptionManager;
+ private final SubscriptionManager mSubscriptionManager;
+ @Nullable
private PreferenceCategory mPreferenceCategory;
private Map<Integer, RestrictedPreference> mPreferences;
- private LifecycleOwner mLifecycleOwner;
- private MobileNetworkRepository mMobileNetworkRepository;
+ private final MobileNetworkRepository mMobileNetworkRepository;
private List<SubscriptionInfoEntity> mSubInfoEntityList = new ArrayList<>();
- private DefaultSubscriptionReceiver mDataSubscriptionChangedReceiver;
+ private final DefaultSubscriptionReceiver mDataSubscriptionChangedReceiver;
- public NetworkProviderSimListController(Context context, Lifecycle lifecycle,
- LifecycleOwner lifecycleOwner) {
- super(context);
+ public NetworkProviderSimListController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
mPreferences = new ArrayMap<>();
- mLifecycleOwner = lifecycleOwner;
mMobileNetworkRepository = MobileNetworkRepository.getInstance(context);
mDataSubscriptionChangedReceiver = new DefaultSubscriptionReceiver(context, this);
- lifecycle.addObserver(this);
}
- @OnLifecycleEvent(ON_RESUME)
- public void onResume() {
- mMobileNetworkRepository.addRegister(mLifecycleOwner, this,
+ @Override
+ public void onResume(@NonNull LifecycleOwner owner) {
+ mMobileNetworkRepository.addRegister(owner, this,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mMobileNetworkRepository.updateEntity();
mDataSubscriptionChangedReceiver.registerReceiver();
}
- @OnLifecycleEvent(ON_PAUSE)
- public void onPause() {
+ @Override
+ public void onPause(@NonNull LifecycleOwner owner) {
mMobileNetworkRepository.removeRegister(this);
mDataSubscriptionChangedReceiver.unRegisterReceiver();
}
@@ -86,7 +77,7 @@
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
- mPreferenceCategory = screen.findPreference(KEY_PREFERENCE_CATEGORY_SIM);
+ mPreferenceCategory = screen.findPreference(getPreferenceKey());
update();
}
@@ -154,31 +145,22 @@
}
@Override
- public boolean isAvailable() {
- if (!getAvailablePhysicalSubscriptions().isEmpty()) {
- return true;
- }
- return false;
+ public int getAvailabilityStatus() {
+ return getAvailablePhysicalSubscriptions().isEmpty()
+ ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
}
@VisibleForTesting
protected List<SubscriptionInfoEntity> getAvailablePhysicalSubscriptions() {
- List<SubscriptionInfoEntity> subList = new ArrayList<>();
- for (SubscriptionInfoEntity info : mSubInfoEntityList) {
- subList.add(info);
- }
- return subList;
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY_PREFERENCE_SIM;
+ return new ArrayList<>(mSubInfoEntityList);
}
@Override
public void onAvailableSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList) {
mSubInfoEntityList = subInfoEntityList;
- mPreferenceCategory.setVisible(isAvailable());
+ if (mPreferenceCategory != null) {
+ mPreferenceCategory.setVisible(isAvailable());
+ }
update();
}
diff --git a/src/com/android/settings/network/NetworkProviderSimsCategoryController.java b/src/com/android/settings/network/NetworkProviderSimsCategoryController.java
deleted file mode 100644
index f983e62..0000000
--- a/src/com/android/settings/network/NetworkProviderSimsCategoryController.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2021 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.network;
-
-import android.content.Context;
-import android.util.Log;
-
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.widget.PreferenceCategoryController;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-
-public class NetworkProviderSimsCategoryController extends PreferenceCategoryController implements
- LifecycleObserver {
- private static final String LOG_TAG = "NetworkProviderSimsCategoryController";
- private static final String KEY_PREFERENCE_CATEGORY_SIM = "provider_model_sim_category";
- private NetworkProviderSimListController mNetworkProviderSimListController;
- private PreferenceCategory mPreferenceCategory;
-
- public NetworkProviderSimsCategoryController(Context context, String key, Lifecycle lifecycle,
- LifecycleOwner lifecycleOwner) {
- super(context, key);
- mNetworkProviderSimListController =
- new NetworkProviderSimListController(mContext, lifecycle, lifecycleOwner);
- }
-
- @Override
- public int getAvailabilityStatus() {
- if (mNetworkProviderSimListController == null
- || !mNetworkProviderSimListController.isAvailable()) {
- return CONDITIONALLY_UNAVAILABLE;
- } else {
- return AVAILABLE;
- }
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mNetworkProviderSimListController.displayPreference(screen);
- mPreferenceCategory = screen.findPreference(KEY_PREFERENCE_CATEGORY_SIM);
- if (mPreferenceCategory == null) {
- Log.d(LOG_TAG, "displayPreference(), Can not find the category.");
- return;
- }
- mPreferenceCategory.setVisible(isAvailable());
- }
-}
diff --git a/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java b/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java
index 9fb80a5..2a355ad 100644
--- a/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java
+++ b/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java
@@ -29,9 +29,12 @@
import android.util.Log;
import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.ListPreference;
+import androidx.preference.ListPreferenceDialogFragmentCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -67,6 +70,7 @@
private SubscriptionsChangeListener mSubscriptionsListener;
private int mCallState = TelephonyManager.CALL_STATE_IDLE;
private PhoneCallStateTelephonyCallback mTelephonyCallback;
+ private FragmentManager mFragmentManager;
public EnabledNetworkModePreferenceController(Context context, String key) {
super(context, key);
@@ -151,7 +155,16 @@
listPreference.setEntryValues(mBuilder.getEntryValues());
listPreference.setValue(Integer.toString(mBuilder.getSelectedEntryValue()));
listPreference.setSummary(mBuilder.getSummary());
- listPreference.setEnabled(isCallStateIdle());
+ boolean listPreferenceEnabled = isCallStateIdle();
+ listPreference.setEnabled(listPreferenceEnabled);
+ if (!listPreferenceEnabled) {
+ // If dialog is already opened when ListPreference disabled, dismiss them.
+ for (Fragment fragment : mFragmentManager.getFragments()) {
+ if (fragment instanceof ListPreferenceDialogFragmentCompat) {
+ ((ListPreferenceDialogFragmentCompat) fragment).dismiss();
+ }
+ }
+ }
}
@Override
@@ -169,8 +182,9 @@
return false;
}
- void init(int subId) {
+ void init(int subId, FragmentManager fragmentManager) {
mSubId = subId;
+ mFragmentManager = fragmentManager;
mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
.createForSubscriptionId(mSubId);
mBuilder = new PreferenceEntriesBuilder(mContext, mSubId);
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
index 452ce49..afc1b7e 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
@@ -260,7 +260,7 @@
use(CarrierPreferenceController.class).init(mSubId);
use(DataUsagePreferenceController.class).init(mSubId);
use(PreferredNetworkModePreferenceController.class).init(mSubId);
- use(EnabledNetworkModePreferenceController.class).init(mSubId);
+ use(EnabledNetworkModePreferenceController.class).init(mSubId, getParentFragmentManager());
use(DataServiceSetupPreferenceController.class).init(mSubId);
use(Enable2gPreferenceController.class).init(mSubId);
use(CarrierWifiTogglePreferenceController.class).init(getLifecycle(), mSubId);
diff --git a/src/com/android/settings/notification/app/NotificationSoundPreference.java b/src/com/android/settings/notification/app/NotificationSoundPreference.java
index 136b21f..b55f9bd 100644
--- a/src/com/android/settings/notification/app/NotificationSoundPreference.java
+++ b/src/com/android/settings/notification/app/NotificationSoundPreference.java
@@ -25,10 +25,13 @@
import android.os.AsyncTask;
import android.util.AttributeSet;
+import android.util.Log;
import com.android.settings.R;
import com.android.settings.RingtonePreference;
public class NotificationSoundPreference extends RingtonePreference {
+ private static final String TAG = "NotificationSoundPreference";
+
private Uri mRingtone;
public NotificationSoundPreference(Context context, AttributeSet attrs) {
@@ -50,8 +53,13 @@
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
if (data != null) {
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
- setRingtone(uri);
- callChangeListener(uri);
+ if (isValidRingtoneUri(uri)) {
+ setRingtone(uri);
+ callChangeListener(uri);
+ } else {
+ Log.e(TAG, "onActivityResult for URI:" + uri
+ + " ignored: invalid ringtone Uri");
+ }
}
return true;
diff --git a/src/com/android/settings/overlay/FeatureFactory.kt b/src/com/android/settings/overlay/FeatureFactory.kt
index 7645076..ac689d9 100644
--- a/src/com/android/settings/overlay/FeatureFactory.kt
+++ b/src/com/android/settings/overlay/FeatureFactory.kt
@@ -21,6 +21,7 @@
import com.android.settings.accounts.AccountFeatureProvider
import com.android.settings.applications.ApplicationFeatureProvider
import com.android.settings.biometrics.face.FaceFeatureProvider
+import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider
import com.android.settings.bluetooth.BluetoothFeatureProvider
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider
@@ -104,9 +105,17 @@
*/
abstract val bluetoothFeatureProvider: BluetoothFeatureProvider
+ /**
+ * Retrieves implementation for Face feature.
+ */
abstract val faceFeatureProvider: FaceFeatureProvider
/**
+ * Retrieves implementation for Fingerprint feature.
+ */
+ abstract val fingerprintFeatureProvider: FingerprintFeatureProvider
+
+ /**
* Gets implementation for Biometrics repository provider.
*/
abstract val biometricsRepositoryProvider: BiometricsRepositoryProvider
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.kt b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
index 0afe9f4..7f991b7 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.kt
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
@@ -29,6 +29,8 @@
import com.android.settings.applications.ApplicationFeatureProviderImpl
import com.android.settings.biometrics.face.FaceFeatureProvider
import com.android.settings.biometrics.face.FaceFeatureProviderImpl
+import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
+import com.android.settings.biometrics.fingerprint.FingerprintFeatureProviderImpl
import com.android.settings.biometrics2.factory.BiometricsRepositoryProviderImpl
import com.android.settings.bluetooth.BluetoothFeatureProvider
import com.android.settings.bluetooth.BluetoothFeatureProviderImpl
@@ -145,6 +147,10 @@
override val faceFeatureProvider: FaceFeatureProvider by lazy { FaceFeatureProviderImpl() }
+ override val fingerprintFeatureProvider: FingerprintFeatureProvider by lazy {
+ FingerprintFeatureProviderImpl()
+ }
+
override val biometricsRepositoryProvider by lazy { BiometricsRepositoryProviderImpl() }
override val wifiTrackerLibProvider: WifiTrackerLibProvider by lazy {
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
index 43d8440..ea00f7f 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
@@ -419,6 +419,10 @@
}
}
+ protected void clearResetErrorRunnable() {
+ mHandler.removeCallbacks(mResetErrorRunnable);
+ }
+
protected void validateGuess(LockscreenCredential credentialGuess) {
mRemoteLockscreenValidationFragment.validateLockscreenGuess(
mRemoteLockscreenValidationClient, credentialGuess,
diff --git a/src/com/android/settings/password/ConfirmLockPassword.java b/src/com/android/settings/password/ConfirmLockPassword.java
index b203015..b139ae9 100644
--- a/src/com/android/settings/password/ConfirmLockPassword.java
+++ b/src/com/android/settings/password/ConfirmLockPassword.java
@@ -666,6 +666,7 @@
}
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ clearResetErrorRunnable();
mCountdownTimer = new CountDownTimer(
elapsedRealtimeDeadline - SystemClock.elapsedRealtime(),
LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
diff --git a/src/com/android/settings/password/ConfirmLockPattern.java b/src/com/android/settings/password/ConfirmLockPattern.java
index 3afb60e..6e3ad30 100644
--- a/src/com/android/settings/password/ConfirmLockPattern.java
+++ b/src/com/android/settings/password/ConfirmLockPattern.java
@@ -697,6 +697,7 @@
}
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ clearResetErrorRunnable();
updateStage(Stage.LockedOut);
long elapsedRealtime = SystemClock.elapsedRealtime();
mCountdownTimer = new CountDownTimer(
diff --git a/tests/robotests/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceControllerTest.java
new file mode 100644
index 0000000..6bf83c6
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceControllerTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.accessibility;
+
+import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
+import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.vibrator.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link KeyboardVibrationTogglePreferenceController}. */
+@RunWith(RobolectricTestRunner.class)
+public class KeyboardVibrationTogglePreferenceControllerTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Mock
+ private PreferenceScreen mPreferenceScreen;
+
+ private Context mContext;
+ private Resources mResources;
+ private KeyboardVibrationTogglePreferenceController mController;
+
+ private SwitchPreference mPreference;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ mResources = spy(mContext.getResources());
+ when(mContext.getResources()).thenReturn(mResources);
+ mController = new KeyboardVibrationTogglePreferenceController(mContext, "preferenceKey");
+ mPreference = new SwitchPreference(mContext);
+ when(mPreferenceScreen.findPreference(
+ mController.getPreferenceKey())).thenReturn(mPreference);
+ mController.displayPreference(mPreferenceScreen);
+ }
+
+ @Test
+ public void getAvailabilityStatus_featureSupported_available() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+ when(mResources.getBoolean(R.bool.config_keyboard_vibration_supported)).thenReturn(true);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_featureNotSupported_unavailable() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+ when(mResources.getBoolean(R.bool.config_keyboard_vibration_supported)).thenReturn(false);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_keyboardCategoryDisabled_unavailable() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
+ when(mResources.getBoolean(R.bool.config_keyboard_vibration_supported)).thenReturn(true);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+ }
+
+ @Test
+ public void updateState_mainVibrateDisabled_shouldReturnFalseForCheckedAndEnabled() {
+ updateSystemSetting(VibrationPreferenceConfig.MAIN_SWITCH_SETTING_KEY, OFF);
+
+ mController.updateState(mPreference);
+
+ assertThat(mPreference.isEnabled()).isFalse();
+ assertThat(mPreference.isChecked()).isFalse();
+ }
+
+ @Test
+ public void updateState_mainVibrateEnabled_shouldReturnTrueForEnabled() {
+ updateSystemSetting(VibrationPreferenceConfig.MAIN_SWITCH_SETTING_KEY, ON);
+
+ mController.updateState(mPreference);
+
+ assertThat(mPreference.isEnabled()).isTrue();
+ }
+
+ @Test
+ public void isChecked_keyboardVibrateEnabled_shouldReturnTrue() {
+ updateSystemSetting(VibrationPreferenceConfig.MAIN_SWITCH_SETTING_KEY, ON);
+ updateSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, ON);
+
+ mController.updateState(mPreference);
+
+ assertThat(mPreference.isChecked()).isTrue();
+ }
+
+ @Test
+ public void isChecked_keyboardVibrateDisabled_shouldReturnFalse() {
+ updateSystemSetting(VibrationPreferenceConfig.MAIN_SWITCH_SETTING_KEY, ON);
+ updateSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, OFF);
+
+ mController.updateState(mPreference);
+
+ assertThat(mPreference.isChecked()).isFalse();
+ }
+
+ @Test
+ public void setChecked_checked_updateSettings() throws Settings.SettingNotFoundException {
+ // set an off state initially
+ updateSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, OFF);
+
+ assertThat(readSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED)).isEqualTo(OFF);
+
+ mController.setChecked(true);
+
+ assertThat(readSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED)).isEqualTo(ON);
+ }
+
+ @Test
+ public void setChecked_unchecked_updateSettings() throws Settings.SettingNotFoundException {
+ // set an on state initially
+ updateSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, ON);
+
+ assertThat(readSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED)).isEqualTo(ON);
+
+ mController.setChecked(false);
+
+ assertThat(readSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED)).isEqualTo(OFF);
+ }
+
+ private void updateSystemSetting(String key, int value) {
+ Settings.System.putInt(mContext.getContentResolver(), key, value);
+ }
+
+ private int readSystemSetting(String key) throws Settings.SettingNotFoundException {
+ return Settings.System.getInt(mContext.getContentResolver(), key);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensorTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensorTest.java
index 72f1ab8..adf76f4 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensorTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensorTest.java
@@ -19,6 +19,7 @@
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
+import static android.text.Layout.HYPHENATION_FREQUENCY_NORMAL;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_SKIP;
@@ -49,6 +50,7 @@
import android.os.Bundle;
import android.os.CancellationSignal;
import android.view.View;
+import android.widget.TextView;
import androidx.fragment.app.Fragment;
@@ -62,6 +64,7 @@
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
+import com.google.android.setupdesign.template.HeaderMixin;
import org.junit.After;
import org.junit.Before;
@@ -568,6 +571,15 @@
assertThat(appliedThemes.contains("SetupWizardPartnerResource")).isTrue();
}
+ @Test
+ public void fingerprintEnrollFindSensor_setHyphenationFrequencyNormalOnHeader() {
+ setupActivity_onUdfpsDevice();
+ PartnerCustomizationLayout layout = mActivity.findViewById(R.id.setup_wizard_layout);
+ final TextView textView = layout.getMixin(HeaderMixin.class).getTextView();
+
+ assertThat(textView.getHyphenationFrequency()).isEqualTo(HYPHENATION_FREQUENCY_NORMAL);
+ }
+
private void triggerEnrollProgressAndError_onRearDevice() {
EnrollmentCallback enrollmentCallback = verifyAndCaptureEnrollmentCallback();
enrollmentCallback.onEnrollmentProgress(123);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
index 2cafadb..ae726b7 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
@@ -62,6 +62,8 @@
private static final int UNINSTALLED_UID = 101;
private static final String PACKAGE_NAME = "com.android.testing";
private static final String UNINSTALLED_PACKAGE_NAME = "com.android.testing.uninstalled";
+ private static final String UID_ZERO_PACKAGE_NAME = "com.android.testing.uid.zero";
+
private Context mContext;
@@ -89,6 +91,9 @@
doReturn(BatteryUtils.UID_NULL)
.when(mMockPackageManager)
.getPackageUid(UNINSTALLED_PACKAGE_NAME, PackageManager.GET_META_DATA);
+ doReturn(BatteryUtils.UID_ZERO)
+ .when(mMockPackageManager)
+ .getPackageUid(UID_ZERO_PACKAGE_NAME, PackageManager.GET_META_DATA);
BatteryDiffEntry.clearCache();
}
@@ -443,6 +448,18 @@
}
@Test
+ public void testIsUninstalledEntry_uidZero_returnFalse() throws Exception {
+ final ContentValues values =
+ getContentValuesWithType(ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
+ values.put(BatteryHistEntry.KEY_UID, BatteryUtils.UID_ZERO);
+ values.put(BatteryHistEntry.KEY_PACKAGE_NAME, PACKAGE_NAME);
+ final BatteryDiffEntry entry = createBatteryDiffEntry(10, new BatteryHistEntry(values));
+
+ assertThat(entry.isSystemEntry()).isFalse();
+ assertThat(entry.isUninstalledEntry()).isFalse();
+ }
+
+ @Test
public void testIsUninstalledEntry_uninstalledApp_returnTrue() throws Exception {
doReturn(BatteryUtils.UID_NULL)
.when(mMockPackageManager)
diff --git a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
index 52a5f24..9156cae 100644
--- a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
@@ -24,6 +24,7 @@
import com.android.settings.accounts.AccountFeatureProvider;
import com.android.settings.applications.ApplicationFeatureProvider;
import com.android.settings.biometrics.face.FaceFeatureProvider;
+import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider;
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
import com.android.settings.bluetooth.BluetoothFeatureProvider;
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider;
@@ -80,6 +81,7 @@
public final AccountFeatureProvider mAccountFeatureProvider;
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
public final FaceFeatureProvider mFaceFeatureProvider;
+ public final FingerprintFeatureProvider mFingerprintFeatureProvider;
public final BiometricsRepositoryProvider mBiometricsRepositoryProvider;
public PanelFeatureProvider panelFeatureProvider;
@@ -132,6 +134,7 @@
panelFeatureProvider = mock(PanelFeatureProvider.class);
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
mFaceFeatureProvider = mock(FaceFeatureProvider.class);
+ mFingerprintFeatureProvider = mock(FingerprintFeatureProvider.class);
mBiometricsRepositoryProvider = mock(BiometricsRepositoryProvider.class);
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
@@ -257,6 +260,11 @@
}
@Override
+ public FingerprintFeatureProvider getFingerprintFeatureProvider() {
+ return mFingerprintFeatureProvider;
+ }
+
+ @Override
public BiometricsRepositoryProvider getBiometricsRepositoryProvider() {
return mBiometricsRepositoryProvider;
}
diff --git a/tests/screenshot/Android.bp b/tests/screenshot/Android.bp
deleted file mode 100644
index 05ceef8..0000000
--- a/tests/screenshot/Android.bp
+++ /dev/null
@@ -1,68 +0,0 @@
-//
-// 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.
-
-android_app {
- name: "ScreenshotTestStub",
- defaults: [
- "SettingsLibDefaults",
- ],
- platform_apis: true,
- certificate: "platform",
- privileged: true,
- use_resource_processor: true,
-
- static_libs: [
- "Settings-core",
- "androidx.fragment_fragment-testing",
- "androidx.fragment_fragment",
- ],
-
- aaptflags: ["--extra-packages com.android.settings"],
-}
-
-android_test {
- name: "SettingsScreenshotTests",
- platform_apis: true,
- certificate: "platform",
- test_suites: ["device-tests"],
- srcs: [
- "src/**/*.kt",
- ],
- static_libs: [
- "androidx.fragment_fragment-testing",
- "androidx.fragment_fragment",
- "androidx.test.rules",
- "androidx.test.ext.junit",
- "platform-screenshot-diff-core",
- "Settings-testutils2",
- "androidx.test.core",
- "androidx.test.espresso.core",
- "kotlinx-coroutines-android",
- "androidx.lifecycle_lifecycle-runtime-testing",
- "kotlinx_coroutines_test",
- "Settings-core",
- ],
- libs: [
- "android.test.runner",
- "android.test.base",
- "android.test.mock",
- ],
- compile_multilib: "both",
- manifest: "AndroidManifest.xml",
- test_config: "AndroidTest.xml",
- use_embedded_native_libs: false,
- asset_dirs: ["assets"],
- instrumentation_for: "ScreenshotTestStub",
-}
diff --git a/tests/screenshot/AndroidManifest.xml b/tests/screenshot/AndroidManifest.xml
deleted file mode 100644
index c9a426c..0000000
--- a/tests/screenshot/AndroidManifest.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="com.android.settings.tests.screenshot"
- >
-
- <application>
- <uses-library android:name="android.test.runner" />
- <provider
- android:name="com.android.settings.slices.SettingsSliceProvider"
- android:authorities="com.android.settings.tests.screenshot.disabled"
- android:enabled="false"
- tools:node="remove"
- tools:replace="android:authorities" />
- </application>
-
- <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
- <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- <uses-permission android:name="android.permission.READ_LOGS" />
- <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
-
- <instrumentation
- android:name="androidx.test.runner.AndroidJUnitRunner"
- android:label="Android Settings Screenshot tests"
- android:targetPackage="com.android.settings.tests.screenshot" />
-
-</manifest>
diff --git a/tests/screenshot/AndroidTest.xml b/tests/screenshot/AndroidTest.xml
deleted file mode 100644
index 7496ffd..0000000
--- a/tests/screenshot/AndroidTest.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<!--
- ~ 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.
- -->
-
-<configuration description="Runs settings screendiff tests.">
- <option name="test-suite-tag" value="apct-instrumentation" />
- <option name="test-suite-tag" value="apct" />
- <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
- <option name="optimized-property-setting" value="true" />
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="SettingsScreenshotTests.apk" />
- </target_preparer>
- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys"
- value="/data/user/0/com.android.settings.tests.screenshot/" />
- <option name="collect-on-run-ended-only" value="true" />
- </metrics_collector>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="com.android.settings.tests.screenshot" />
- <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
- </test>
-</configuration>
diff --git "a/tests/screenshot/assets/pixel_4a_\0505g\051/fp_enroll_intro.png" "b/tests/screenshot/assets/pixel_4a_\0505g\051/fp_enroll_intro.png"
deleted file mode 100644
index 1129250..0000000
--- "a/tests/screenshot/assets/pixel_4a_\0505g\051/fp_enroll_intro.png"
+++ /dev/null
Binary files differ
diff --git a/tests/screenshot/src/com/android/settings/tests/screenshot/BasicScreenshotTest.kt b/tests/screenshot/src/com/android/settings/tests/screenshot/BasicScreenshotTest.kt
deleted file mode 100644
index 3b3b170..0000000
--- a/tests/screenshot/src/com/android/settings/tests/screenshot/BasicScreenshotTest.kt
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * 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.tests.screenshot
-
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Color
-import android.os.Bundle
-import android.view.View
-import androidx.fragment.app.testing.FragmentScenario
-import androidx.fragment.app.testing.launchFragmentInContainer
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
-import com.android.settings.testutils2.FakeFingerprintManagerInteractor
-import kotlinx.coroutines.test.StandardTestDispatcher
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import platform.test.screenshot.GoldenImagePathManager
-import platform.test.screenshot.ScreenshotTestRule
-import platform.test.screenshot.matchers.MSSIMMatcher
-
-@RunWith(AndroidJUnit4::class)
-class BasicScreenshotTest {
- @Rule
- @JvmField
- var rule: ScreenshotTestRule =
- ScreenshotTestRule(
- GoldenImagePathManager(
- InstrumentationRegistry.getInstrumentation().getContext(),
- InstrumentationRegistry.getInstrumentation()
- .getTargetContext()
- .getFilesDir()
- .getAbsolutePath() + "/settings_screenshots"
- )
- )
-
- private var context: Context = ApplicationProvider.getApplicationContext()
- private var interactor = FakeFingerprintManagerInteractor()
-
- private val gatekeeperViewModel =
- FingerprintGatekeeperViewModel(
- GatekeeperInfo.GatekeeperPasswordInfo(byteArrayOf(1, 2, 3), 100L),
- interactor
- )
-
- private val backgroundDispatcher = StandardTestDispatcher()
- private lateinit var fragmentScenario: FragmentScenario<FingerprintEnrollIntroV2Fragment>
-
- private val navigationViewModel =
- FingerprintEnrollNavigationViewModel(
- backgroundDispatcher,
- interactor,
- gatekeeperViewModel,
- canSkipConfirm = true,
- )
- private var fingerprintViewModel = FingerprintEnrollViewModel(interactor, backgroundDispatcher)
- private var fingerprintScrollViewModel = FingerprintScrollViewModel()
-
- @Before
- fun setup() {
- val factory =
- object : ViewModelProvider.Factory {
- @Suppress("UNCHECKED_CAST")
- override fun <T : ViewModel> create(
- modelClass: Class<T>,
- ): T {
- return when (modelClass) {
- FingerprintEnrollViewModel::class.java -> fingerprintViewModel
- FingerprintScrollViewModel::class.java -> fingerprintScrollViewModel
- FingerprintEnrollNavigationViewModel::class.java -> navigationViewModel
- FingerprintGatekeeperViewModel::class.java -> gatekeeperViewModel
- else -> null
- }
- as T
- }
- }
-
- fragmentScenario =
- launchFragmentInContainer(Bundle(), R.style.SudThemeGlif) {
- FingerprintEnrollIntroV2Fragment(factory)
- }
- }
-
- /** Renders a [view] into a [Bitmap]. */
- private fun viewToBitmap(view: View): Bitmap {
- val bitmap =
- Bitmap.createBitmap(
- view.measuredWidth,
- view.measuredHeight,
- Bitmap.Config.ARGB_8888,
- )
- val canvas = Canvas(bitmap)
- view.draw(canvas)
- return bitmap
- }
-
- @Test
- fun testEnrollIntro() {
- fragmentScenario.onFragment { fragment ->
- val view = fragment.requireView().findViewById<View>(R.id.enroll_intro_content_view)!!
- view.setBackgroundColor(Color.BLACK)
- }
- fragmentScenario.onFragment { fragment ->
- val view = fragment.requireView().findViewById<View>(R.id.enroll_intro_content_view)!!
- rule.assertBitmapAgainstGolden(
- viewToBitmap(view),
- "fp_enroll_intro",
- MSSIMMatcher()
- )
- }
-
- }
-}
diff --git a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
index 95f25ad..54299eb 100644
--- a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
+++ b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
@@ -22,6 +22,7 @@
import com.android.settings.accounts.AccountFeatureProvider
import com.android.settings.applications.ApplicationFeatureProvider
import com.android.settings.biometrics.face.FaceFeatureProvider
+import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider
import com.android.settings.bluetooth.BluetoothFeatureProvider
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider
@@ -120,6 +121,8 @@
get() = TODO("Not yet implemented")
override val faceFeatureProvider: FaceFeatureProvider
get() = TODO("Not yet implemented")
+ override val fingerprintFeatureProvider: FingerprintFeatureProvider
+ get() = TODO("Not yet implemented")
override val biometricsRepositoryProvider: BiometricsRepositoryProvider
get() = TODO("Not yet implemented")
override val wifiTrackerLibProvider: WifiTrackerLibProvider
diff --git a/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java b/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java
index 7877684..c580fc5 100644
--- a/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java
+++ b/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java
@@ -16,16 +16,20 @@
package com.android.settings;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.ContentInterface;
import android.content.ContentResolver;
import android.content.Context;
-import android.media.RingtoneManager;
+import android.content.pm.UserProperties;
import android.net.Uri;
+import android.os.UserHandle;
+import android.os.UserManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -34,17 +38,22 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
/** Unittest for DefaultRingtonePreference. */
@RunWith(AndroidJUnit4.class)
public class DefaultRingtonePreferenceTest {
+ private static final int OWNER_USER_ID = 1;
+ private static final int OTHER_USER_ID = 10;
+ private static final int INVALID_RINGTONE_TYPE = 0;
private DefaultRingtonePreference mDefaultRingtonePreference;
@Mock
private ContentResolver mContentResolver;
@Mock
+ private UserManager mUserManager;
private Uri mRingtoneUri;
@Before
@@ -52,14 +61,29 @@
MockitoAnnotations.initMocks(this);
Context context = spy(ApplicationProvider.getApplicationContext());
- doReturn(mContentResolver).when(context).getContentResolver();
+ mContentResolver = ContentResolver.wrap(Mockito.mock(ContentInterface.class));
+ when(context.getContentResolver()).thenReturn(mContentResolver);
mDefaultRingtonePreference = spy(new DefaultRingtonePreference(context, null /* attrs */));
doReturn(context).when(mDefaultRingtonePreference).getContext();
+
+ // Use INVALID_RINGTONE_TYPE to return early in RingtoneManager.setActualDefaultRingtoneUri
when(mDefaultRingtonePreference.getRingtoneType())
- .thenReturn(RingtoneManager.TYPE_RINGTONE);
- mDefaultRingtonePreference.setUserId(1);
+ .thenReturn(INVALID_RINGTONE_TYPE);
+
+ mDefaultRingtonePreference.setUserId(OWNER_USER_ID);
mDefaultRingtonePreference.mUserContext = context;
+ when(mDefaultRingtonePreference.isDefaultRingtone(any(Uri.class))).thenReturn(false);
+
+ when(context.getSystemServiceName(UserManager.class)).thenReturn(Context.USER_SERVICE);
+ when(context.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+ UserProperties userProperties = new UserProperties.Builder().setMediaSharedWithParent(false)
+ .build();
+ when(mUserManager.getUserProperties(UserHandle.of(OTHER_USER_ID))).thenReturn(
+ userProperties);
+
+ mRingtoneUri = Uri.parse("content://none");
}
@Test
@@ -79,4 +103,53 @@
verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri);
}
+
+ @Test
+ public void onSaveRingtone_notManagedProfile_shouldNotSetRingtone() {
+ mRingtoneUri = Uri.parse("content://" + OTHER_USER_ID + "@ringtone");
+ when(mContentResolver.getType(mRingtoneUri)).thenReturn("audio/*");
+ when(mUserManager.isSameProfileGroup(OWNER_USER_ID, OTHER_USER_ID)).thenReturn(true);
+ when(mUserManager.getProfileParent(UserHandle.of(OTHER_USER_ID))).thenReturn(
+ UserHandle.of(OWNER_USER_ID));
+ when(mUserManager.isManagedProfile(OTHER_USER_ID)).thenReturn(false);
+
+ mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri);
+
+ verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri);
+ }
+
+ @Test
+ public void onSaveRingtone_notSameUser_shouldNotSetRingtone() {
+ mRingtoneUri = Uri.parse("content://" + OTHER_USER_ID + "@ringtone");
+ when(mContentResolver.getType(mRingtoneUri)).thenReturn("audio/*");
+ when(mUserManager.isSameProfileGroup(OWNER_USER_ID, OTHER_USER_ID)).thenReturn(false);
+
+ mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri);
+
+ verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri);
+ }
+
+ @Test
+ public void onSaveRingtone_isManagedProfile_shouldSetRingtone() {
+ mRingtoneUri = Uri.parse("content://" + OTHER_USER_ID + "@ringtone");
+ when(mContentResolver.getType(mRingtoneUri)).thenReturn("audio/*");
+ when(mUserManager.isSameProfileGroup(OWNER_USER_ID, OTHER_USER_ID)).thenReturn(true);
+ when(mUserManager.getProfileParent(UserHandle.of(OTHER_USER_ID))).thenReturn(
+ UserHandle.of(OWNER_USER_ID));
+ when(mUserManager.isManagedProfile(OTHER_USER_ID)).thenReturn(true);
+
+ mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri);
+
+ verify(mDefaultRingtonePreference).setActualDefaultRingtoneUri(mRingtoneUri);
+ }
+
+ @Test
+ public void onSaveRingtone_defaultUri_shouldSetRingtone() {
+ mRingtoneUri = Uri.parse("default_ringtone");
+ when(mDefaultRingtonePreference.isDefaultRingtone(any(Uri.class))).thenReturn(true);
+
+ mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri);
+
+ verify(mDefaultRingtonePreference).setActualDefaultRingtoneUri(mRingtoneUri);
+ }
}
diff --git a/tests/unit/src/com/android/settings/network/NetworkProviderSimListControllerTest.java b/tests/unit/src/com/android/settings/network/NetworkProviderSimListControllerTest.java
index c4e0f64..a98f83b 100644
--- a/tests/unit/src/com/android/settings/network/NetworkProviderSimListControllerTest.java
+++ b/tests/unit/src/com/android/settings/network/NetworkProviderSimListControllerTest.java
@@ -16,19 +16,16 @@
package com.android.settings.network;
-import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
-import static com.google.common.truth.Truth.assertThat;
-
import static androidx.lifecycle.Lifecycle.Event;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -36,18 +33,15 @@
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
-import androidx.preference.PreferenceManager;
-import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.settings.R;
import com.android.settings.testutils.ResourcesUtils;
import com.android.settingslib.RestrictedPreference;
-import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
import org.junit.Before;
@@ -83,8 +77,6 @@
@Mock
private SubscriptionManager mSubscriptionManager;
@Mock
- private Lifecycle mLifecycle;
- @Mock
private LifecycleOwner mLifecycleOwner;
private LifecycleRegistry mLifecycleRegistry;
@@ -100,12 +92,10 @@
* Mock the NetworkProviderSimListController that allows one to set a default voice,
* SMS and mobile data subscription ID.
*/
- @SuppressWarnings("ClassCanBeStatic")
- private class MockNetworkProviderSimListController extends
- com.android.settings.network.NetworkProviderSimListController {
- public MockNetworkProviderSimListController(Context context, Lifecycle lifecycle,
- LifecycleOwner lifecycleOwner) {
- super(context, lifecycle, lifecycleOwner);
+ private static class MockNetworkProviderSimListController
+ extends NetworkProviderSimListController {
+ MockNetworkProviderSimListController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
}
private List<SubscriptionInfoEntity> mSubscriptionInfoEntity;
@@ -136,8 +126,7 @@
mPreference.setKey(KEY_PREFERENCE_SIM_LIST);
mPreferenceCategory = new PreferenceCategory(mContext);
mPreferenceCategory.setKey(KEY_PREFERENCE_CATEGORY_SIM);
- mController = new MockNetworkProviderSimListController(mContext, mLifecycle,
- mLifecycleOwner);
+ mController = new MockNetworkProviderSimListController(mContext, "test_key");
mLifecycleRegistry = new LifecycleRegistry(mLifecycleOwner);
when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycleRegistry);
}
diff --git a/tests/unit/src/com/android/settings/network/NetworkProviderSimsCategoryControllerTest.java b/tests/unit/src/com/android/settings/network/NetworkProviderSimsCategoryControllerTest.java
deleted file mode 100644
index dc17e91..0000000
--- a/tests/unit/src/com/android/settings/network/NetworkProviderSimsCategoryControllerTest.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2020 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.network;
-
-import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.os.Looper;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-
-import com.android.settings.testutils.ResourcesUtils;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceManager;
-import androidx.preference.PreferenceScreen;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-//TODO: Remove NetworkProviderSimsCategoryControllerTest once it is removed in the b/244769887.
-@RunWith(AndroidJUnit4.class)
-public class NetworkProviderSimsCategoryControllerTest {
-
- private static final String KEY_PREFERENCE_CATEGORY_SIM = "provider_model_sim_category";
- private static final String SUB_1 = "SUB_1";
- private static final String SUB_2 = "SUB_2";
- private static final int SUB_ID_1 = 1;
- private static final int SUB_ID_2 = 2;
-
- @Mock
- private Lifecycle mLifecycle;
- @Mock
- private SubscriptionInfo mSubscriptionInfo1;
- @Mock
- private SubscriptionInfo mSubscriptionInfo2;
-
- private Context mContext;
- private NetworkProviderSimsCategoryController mCategoryController;
- private PreferenceManager mPreferenceManager;
- private PreferenceScreen mPreferenceScreen;
- private PreferenceCategory mPreferenceCategory;
- private LifecycleOwner mLifecycleOwner;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mContext = spy(ApplicationProvider.getApplicationContext());
-
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
- mLifecycleOwner = () -> mLifecycle;
- mPreferenceManager = new PreferenceManager(mContext);
- mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
- mPreferenceCategory = new PreferenceCategory(mContext);
- mPreferenceCategory.setKey(KEY_PREFERENCE_CATEGORY_SIM);
- mPreferenceScreen.addPreference(mPreferenceCategory);
-
- mCategoryController = new NetworkProviderSimsCategoryController(
- mContext, KEY_PREFERENCE_CATEGORY_SIM, mLifecycle, mLifecycleOwner);
- }
-
- @Ignore
- @Test
- public void getAvailabilityStatus_returnUnavailable() {
- SubscriptionUtil.setAvailableSubscriptionsForTesting(new ArrayList<>());
-
- assertThat(mCategoryController.getAvailabilityStatus()).isEqualTo(
- CONDITIONALLY_UNAVAILABLE);
- }
-
- @Ignore
- @Test
- public void displayPreference_isVisible() {
- setUpSubscriptionInfoForPhysicalSim(SUB_ID_1, SUB_1, mSubscriptionInfo1);
- SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscriptionInfo1));
- mCategoryController.displayPreference(mPreferenceScreen);
-
- assertEquals(mPreferenceCategory.isVisible(), true);
- }
-
- @Ignore
- @Test
- public void updateState_setTitle_withTwoPhysicalSims_returnSims() {
- setUpSubscriptionInfoForPhysicalSim(SUB_ID_1, SUB_1, mSubscriptionInfo1);
- setUpSubscriptionInfoForPhysicalSim(SUB_ID_2, SUB_2, mSubscriptionInfo2);
- SubscriptionUtil.setAvailableSubscriptionsForTesting(
- Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
-
- mCategoryController.displayPreference(mPreferenceScreen);
- mCategoryController.updateState(mPreferenceCategory);
-
- assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2);
- assertThat(mPreferenceCategory.getTitle()).isEqualTo(
- ResourcesUtils.getResourcesString(mContext, "provider_network_settings_title"));
- }
-
- @Ignore
- @Test
- public void updateState_setTitle_withOnePhysicalSim_returnSim() {
- setUpSubscriptionInfoForPhysicalSim(SUB_ID_1, SUB_1, mSubscriptionInfo1);
- SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mSubscriptionInfo1));
-
- mCategoryController.displayPreference(mPreferenceScreen);
- mCategoryController.updateState(mPreferenceCategory);
-
- assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(1);
- assertThat(mPreferenceCategory.getTitle()).isEqualTo(
- ResourcesUtils.getResourcesString(mContext, "sim_category_title"));
- }
-
- private void setUpSubscriptionInfoForPhysicalSim(int subId, String displayName,
- SubscriptionInfo subscriptionInfo) {
- when(subscriptionInfo.isEmbedded()).thenReturn(false);
- when(subscriptionInfo.getSubscriptionId()).thenReturn(subId);
- when(subscriptionInfo.getDisplayName()).thenReturn(displayName);
- }
-
-}
diff --git a/tests/unit/src/com/android/settings/network/EnabledNetworkModePreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceControllerTest.java
similarity index 96%
rename from tests/unit/src/com/android/settings/network/EnabledNetworkModePreferenceControllerTest.java
rename to tests/unit/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceControllerTest.java
index 7f170b5..1b337ca 100644
--- a/tests/unit/src/com/android/settings/network/EnabledNetworkModePreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceControllerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -46,6 +46,7 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceManager;
@@ -64,6 +65,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Collections;
+
@RunWith(AndroidJUnit4.class)
public class EnabledNetworkModePreferenceControllerTest {
private static final int SUB_ID = 2;
@@ -80,6 +83,8 @@
private CarrierConfigCache mCarrierConfigCache;
@Mock
private ServiceState mServiceState;
+ @Mock
+ private FragmentManager mFragmentManager;
private PersistableBundle mPersistableBundle;
private EnabledNetworkModePreferenceController mController;
@@ -111,7 +116,8 @@
mController = new EnabledNetworkModePreferenceController(mContext, KEY);
mockAllowedNetworkTypes(ALLOWED_ALL_NETWORK_TYPE);
mockAccessFamily(TelephonyManager.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
- mController.init(SUB_ID);
+ when(mFragmentManager.getFragments()).thenReturn(Collections.emptyList());
+ mController.init(SUB_ID, mFragmentManager);
mPreference.setKey(mController.getPreferenceKey());
}
@@ -205,7 +211,7 @@
public void updateState_5gWorldPhone_GlobalHasNr() {
mockAllowedNetworkTypes(ALLOWED_ALL_NETWORK_TYPE);
mockAccessFamily(TelephonyManager.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA);
- mController.init(SUB_ID);
+ mController.init(SUB_ID, mFragmentManager);
mPersistableBundle.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, true);
mController.updateState(mPreference);
@@ -221,7 +227,7 @@
mockAllowedNetworkTypes(ALLOWED_ALL_NETWORK_TYPE);
mockEnabledNetworkMode(TelephonyManagerConstants.NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA);
mockAccessFamily(TelephonyManager.NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA);
- mController.init(SUB_ID);
+ mController.init(SUB_ID, mFragmentManager);
// NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA = NR | LTE | RAF_TD_SCDMA | GSM | WCDMA
when(mTelephonyManager.getAllowedNetworkTypesForReason(
@@ -241,7 +247,7 @@
mockEnabledNetworkMode(TelephonyManagerConstants.NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA);
mockAccessFamily(TelephonyManager.NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA);
mockAllowedNetworkTypes(DISABLED_5G_NETWORK_TYPE);
- mController.init(SUB_ID);
+ mController.init(SUB_ID, mFragmentManager);
// NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA = NR | LTE | RAF_TD_SCDMA | GSM | WCDMA
when(mTelephonyManager.getAllowedNetworkTypesForReason(
@@ -261,7 +267,7 @@
mockEnabledNetworkMode(TelephonyManagerConstants.NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA);
mockAccessFamily(TelephonyManager.NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA);
mockAllowedNetworkTypes(DISABLED_5G_NETWORK_TYPE);
- mController.init(SUB_ID);
+ mController.init(SUB_ID, mFragmentManager);
// NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA = NR | LTE | RAF_TD_SCDMA | GSM | WCDMA
when(mTelephonyManager.getAllowedNetworkTypesForReason(
@@ -281,7 +287,7 @@
mockAccessFamily(TelephonyManager.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA);
mockAllowedNetworkTypes(DISABLED_5G_NETWORK_TYPE);
- mController.init(SUB_ID);
+ mController.init(SUB_ID, mFragmentManager);
// NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA = LTE | CDMA | EVDO | GSM | WCDMA
when(mTelephonyManager.getAllowedNetworkTypesForReason(
@@ -304,7 +310,7 @@
public void updateState_GlobalDisAllowed5g_GlobalWithoutNR() {
mockAccessFamily(TelephonyManager.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA);
mockAllowedNetworkTypes(DISABLED_5G_NETWORK_TYPE);
- mController.init(SUB_ID);
+ mController.init(SUB_ID, mFragmentManager);
mPersistableBundle.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, true);
// NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA = NR | LTE | CDMA | EVDO | GSM | WCDMA
@@ -324,7 +330,7 @@
public void updateState_GlobalDisAllowed5g_SelectOnGlobal() {
mockAccessFamily(TelephonyManager.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA);
mockAllowedNetworkTypes(DISABLED_5G_NETWORK_TYPE);
- mController.init(SUB_ID);
+ mController.init(SUB_ID, mFragmentManager);
mPersistableBundle.putBoolean(CarrierConfigManager.KEY_WORLD_MODE_ENABLED_BOOL, true);
// NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA = NR | LTE | CDMA | EVDO | GSM | WCDMA
@@ -497,7 +503,7 @@
mPersistableBundle.putBoolean(CarrierConfigManager.KEY_PREFER_2G_BOOL, true);
mPersistableBundle.putBoolean(CarrierConfigManager.KEY_LTE_ENABLED_BOOL, true);
}
- mController.init(SUB_ID);
+ mController.init(SUB_ID, mFragmentManager);
}
private void mockAllowedNetworkTypes(long allowedNetworkType) {
diff --git a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
index a3a92a3..b5062a0 100644
--- a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
@@ -24,6 +24,7 @@
import com.android.settings.accounts.AccountFeatureProvider;
import com.android.settings.applications.ApplicationFeatureProvider;
import com.android.settings.biometrics.face.FaceFeatureProvider;
+import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider;
import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
import com.android.settings.bluetooth.BluetoothFeatureProvider;
import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider;
@@ -79,6 +80,7 @@
public final AccountFeatureProvider mAccountFeatureProvider;
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
public final FaceFeatureProvider mFaceFeatureProvider;
+ public final FingerprintFeatureProvider mFingerprintFeatureProvider;
public final BiometricsRepositoryProvider mBiometricsRepositoryProvider;
public PanelFeatureProvider panelFeatureProvider;
@@ -131,6 +133,7 @@
panelFeatureProvider = mock(PanelFeatureProvider.class);
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
mFaceFeatureProvider = mock(FaceFeatureProvider.class);
+ mFingerprintFeatureProvider = mock(FingerprintFeatureProvider.class);
mBiometricsRepositoryProvider = mock(BiometricsRepositoryProvider.class);
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
@@ -256,6 +259,11 @@
}
@Override
+ public FingerprintFeatureProvider getFingerprintFeatureProvider() {
+ return mFingerprintFeatureProvider;
+ }
+
+ @Override
public BiometricsRepositoryProvider getBiometricsRepositoryProvider() {
return mBiometricsRepositoryProvider;
}