diff --git a/res/drawable/ic_head_tracking.xml b/res/drawable/ic_head_tracking.xml
new file mode 100644
index 00000000000..d4a44fd9858
--- /dev/null
+++ b/res/drawable/ic_head_tracking.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/res/drawable/ic_spatial_audio.xml b/res/drawable/ic_spatial_audio.xml
new file mode 100644
index 00000000000..0ee609ab79f
--- /dev/null
+++ b/res/drawable/ic_spatial_audio.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/res/drawable/ic_spatial_audio_off.xml b/res/drawable/ic_spatial_audio_off.xml
new file mode 100644
index 00000000000..c7d3272b380
--- /dev/null
+++ b/res/drawable/ic_spatial_audio_off.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/res/layout/preference_circular_icons.xml b/res/layout/preference_circular_icons.xml
index ae981b2c562..863d2288513 100644
--- a/res/layout/preference_circular_icons.xml
+++ b/res/layout/preference_circular_icons.xml
@@ -61,6 +61,7 @@
2
-
- - @*android:drawable/ic_zen_mode_type_bedtime
- - @*android:drawable/ic_zen_mode_type_driving
- - @*android:drawable/ic_zen_mode_type_immersive
+
+ - @*android:drawable/ic_zen_mode_icon_work
+ - @*android:drawable/ic_zen_mode_icon_classical_building
+ - @*android:drawable/ic_zen_mode_icon_apartment_building
+ - @*android:drawable/ic_zen_mode_icon_speech_bubble
+ - @*android:drawable/ic_zen_mode_icon_group_of_people
+ - @*android:drawable/ic_zen_mode_icon_lightbulb
- @*android:drawable/ic_zen_mode_type_schedule_calendar
- - @*android:drawable/ic_zen_mode_type_schedule_time
- - @*android:drawable/ic_zen_mode_icon_beach
- - @*android:drawable/ic_zen_mode_icon_camping
- - @*android:drawable/ic_zen_mode_type_theater
- - @*android:drawable/ic_zen_mode_icon_gaming
+
+ - @*android:drawable/ic_zen_mode_icon_running
+ - @*android:drawable/ic_zen_mode_icon_golf
- @*android:drawable/ic_zen_mode_icon_gym
- - @*android:drawable/ic_zen_mode_icon_ball_sports
- - @*android:drawable/ic_zen_mode_icon_martial_arts
- @*android:drawable/ic_zen_mode_icon_swimming
- @*android:drawable/ic_zen_mode_icon_hiking
- - @*android:drawable/ic_zen_mode_icon_golf
+ - @*android:drawable/ic_zen_mode_icon_ball_sports
+ - @*android:drawable/ic_zen_mode_icon_martial_arts
+
+ - @*android:drawable/ic_zen_mode_icon_gaming
+ - @*android:drawable/ic_zen_mode_icon_palette
+ - @*android:drawable/ic_zen_mode_icon_snowflake
+ - @*android:drawable/ic_zen_mode_icon_beach
- @*android:drawable/ic_zen_mode_icon_workshop
- - @*android:drawable/ic_zen_mode_icon_work
- - @*android:drawable/ic_zen_mode_type_other
- - @*android:drawable/ic_zen_mode_type_unknown
- - @*android:drawable/ic_zen_mode_type_managed
+ - @*android:drawable/ic_zen_mode_icon_camping
+ - @*android:drawable/ic_zen_mode_type_theater
+ - @*android:drawable/ic_zen_mode_icon_book
+
+ - @*android:drawable/ic_zen_mode_type_unknown
+ - @*android:drawable/ic_zen_mode_type_immersive
+ - @*android:drawable/ic_zen_mode_icon_headphones
+ - @*android:drawable/ic_zen_mode_icon_tv
+
+ - @*android:drawable/ic_zen_mode_icon_train
+ - @*android:drawable/ic_zen_mode_type_driving
+ - @*android:drawable/ic_zen_mode_icon_croissant
+ - @*android:drawable/ic_zen_mode_icon_fork_and_knife
+ - @*android:drawable/ic_zen_mode_icon_shopping_cart
+ - @*android:drawable/ic_zen_mode_icon_child
+ - @*android:drawable/ic_zen_mode_icon_rabbit
+ - @*android:drawable/ic_zen_mode_icon_animal_paw
+
+ - @*android:drawable/ic_zen_mode_type_managed
+ - @*android:drawable/ic_zen_mode_type_other
+ - @*android:drawable/ic_zen_mode_icon_heart
+ - @*android:drawable/ic_zen_mode_icon_house
+ - @*android:drawable/ic_zen_mode_type_bedtime
+ - @*android:drawable/ic_zen_mode_type_schedule_time
-
- - Half-moon
- - Car
- - Person\'s mind
+
+ - Briefcase
+ - Classical building
+ - Apartment building
+ - Speech bubble
+ - Group of people
+ - Lightbulb
- Calendar
- - Clock
- - Beach umbrella
- - Tent
- - Film reel
- - Game controller
+
+ - Person running
+ - Golf
- Gym dumbbell
- - Person throwing ball
- - Person kicking
- Swimming
- Person hiking
- - Golf
+ - Person throwing ball
+ - Person kicking
+
+ - Game controller
+ - Artist color palette
+ - Snowflake
+ - Beach umbrella
- Workshop tools
- - Briefcase
- - Star
+ - Tent
+ - Film reel
+ - Book
+
- Lotus flower
+ - Person\'s mind
+ - Headphones
+ - TV
+
+ - Train
+ - Car
+ - Croissant
+ - Fork and knife
+ - Shopping cart
+ - Child
+ - Rabbit
+ - Animal paw
+
- Supervisor
+ - Star
+ - Heart
+ - House
+ - Half-moon
+ - Clock
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6c018c23ec9..4b30dc19abe 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7946,6 +7946,18 @@
Connected devices settings
+
+ Spatial Audio
+
+
+ Off
+
+
+ Off
+
+
+ Off
+
{count, plural,
diff --git a/src/com/android/settings/MainClear.java b/src/com/android/settings/MainClear.java
index ab7a7146329..711d7943ffa 100644
--- a/src/com/android/settings/MainClear.java
+++ b/src/com/android/settings/MainClear.java
@@ -183,13 +183,16 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis
if (requestCode == KEYGUARD_REQUEST) {
final int userId = getActivity().getUserId();
- if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
- false /* biometricsSuccessfullyAuthenticated */,
- false /* biometricsAuthenticationRequested */,
- userId)) {
+ final Utils.BiometricStatus biometricAuthStatus =
+ Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
+ false /* biometricsAuthenticationRequested */,
+ userId);
+ if (biometricAuthStatus == Utils.BiometricStatus.OK) {
Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRICS_REQUEST,
userId, false /* hideBackground */);
return;
+ } else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
+ return;
}
}
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index badcb63791c..3646938e1f8 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -199,6 +199,15 @@ public final class Utils extends com.android.settingslib.Utils {
return ActivityManager.isUserAMonkey();
}
+ /**
+ * Enum for returning biometric status.
+ * {@link OK} no error detected when requesting mandatory biometrics authentication
+ * {@link NOT_ACTIVE} mandatory biometrics is not active
+ * {@link LOCKOUT} biometric sensors are in lockout mode
+ * {@link ERROR} corresponds to other errors
+ */
+ public enum BiometricStatus {OK, NOT_ACTIVE, LOCKOUT, ERROR}
+
/**
* Returns whether the device is voice-capable (meaning, it is also a phone).
*/
@@ -1489,34 +1498,41 @@ public final class Utils extends com.android.settingslib.Utils {
/**
* Request biometric authentication if all requirements for mandatory biometrics is satisfied.
*
- * @param context of the corresponding activity/fragment
- * @param biometricsSuccessfullyAuthenticated if the user has already authenticated using
- * biometrics
- * @param biometricsAuthenticationRequested if the activity/fragment has already requested for
- * biometric prompt
- * @param userId user id for the authentication request
- * @return true if all requirements for mandatory biometrics is satisfied
+ * @param context of the corresponding activity/fragment
+ * @param biometricsAuthenticationRequested if the activity/fragment has already requested for
+ * biometric prompt
+ * @param userId user id for the authentication request
+ * @return biometric status when mandatory biometrics authentication is requested
*/
- public static boolean requestBiometricAuthenticationForMandatoryBiometrics(
+ public static BiometricStatus requestBiometricAuthenticationForMandatoryBiometrics(
@NonNull Context context,
- boolean biometricsSuccessfullyAuthenticated,
boolean biometricsAuthenticationRequested, int userId) {
final BiometricManager biometricManager = context.getSystemService(BiometricManager.class);
if (biometricManager == null) {
Log.e(TAG, "Biometric Manager is null.");
- return false;
+ return BiometricStatus.NOT_ACTIVE;
}
final int status = biometricManager.canAuthenticate(userId,
BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
- return android.hardware.biometrics.Flags.mandatoryBiometrics()
- && status == BiometricManager.BIOMETRIC_SUCCESS
- && !biometricsSuccessfullyAuthenticated
- && !biometricsAuthenticationRequested;
+ if (android.hardware.biometrics.Flags.mandatoryBiometrics()
+ && !biometricsAuthenticationRequested) {
+ switch(status) {
+ case BiometricManager.BIOMETRIC_SUCCESS:
+ return BiometricStatus.OK;
+ case BiometricManager.BIOMETRIC_ERROR_LOCKOUT:
+ return BiometricStatus.LOCKOUT;
+ case BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE:
+ return BiometricStatus.NOT_ACTIVE;
+ default:
+ return BiometricStatus.ERROR;
+ }
+ }
+ return BiometricStatus.NOT_ACTIVE;
}
/**
* Launch biometric prompt for mandatory biometrics. Call
- * {@link #requestBiometricAuthenticationForMandatoryBiometrics(Context, boolean, boolean, int)}
+ * {@link #requestBiometricAuthenticationForMandatoryBiometrics(Context, boolean, int)}
* to check if all requirements for mandatory biometrics is satisfied
* before launching biometric prompt.
*
diff --git a/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java b/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java
index 833638bb2e0..818eb5e4bab 100644
--- a/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java
+++ b/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java
@@ -110,7 +110,7 @@ public class KeyboardVibrationTogglePreferenceController extends TogglePreferenc
@Override
public int getAvailabilityStatus() {
if (mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_keyboardVibrationSettingsSupported)) {
+ com.android.internal.R.bool.config_keyboardVibrationSettingsSupported)) {
return AVAILABLE;
}
return UNSUPPORTED_ON_DEVICE;
@@ -128,15 +128,9 @@ public class KeyboardVibrationTogglePreferenceController extends TogglePreferenc
mMetricsFeatureProvider.action(mContext,
SettingsEnums.ACTION_KEYBOARD_VIBRATION_CHANGED, 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);
+ // Play the preview vibration effect for the IME feedback when the toggle is on.
+ VibrationPreferenceConfig.playVibrationPreview(
+ mVibrator, VibrationAttributes.USAGE_IME_FEEDBACK);
}
return true;
}
diff --git a/src/com/android/settings/accessibility/VibrationPreferenceConfig.java b/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
index a3048622fb0..ec1fab1af72 100644
--- a/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
+++ b/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
@@ -68,19 +68,8 @@ public abstract class VibrationPreferenceConfig {
/** Play a vibration effect with intensity just selected by the user. */
public static void playVibrationPreview(Vibrator vibrator,
@VibrationAttributes.Usage int 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);
+ vibrator.vibrate(PREVIEW_VIBRATION_EFFECT,
+ createPreviewVibrationAttributes(vibrationUsage));
}
public VibrationPreferenceConfig(Context context, String settingKey,
diff --git a/src/com/android/settings/applications/appinfo/AppLocaleDetails.java b/src/com/android/settings/applications/appinfo/AppLocaleDetails.java
index 1e7ca1e5802..b40f62f4169 100644
--- a/src/com/android/settings/applications/appinfo/AppLocaleDetails.java
+++ b/src/com/android/settings/applications/appinfo/AppLocaleDetails.java
@@ -33,6 +33,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.internal.app.LocaleHelper;
@@ -62,6 +63,7 @@ public class AppLocaleDetails extends SettingsPreferenceFragment {
private LayoutPreference mPrefOfDescription;
private Preference mPrefOfDisclaimer;
private ApplicationInfo mApplicationInfo;
+ @Nullable private String mParentLocale;
/**
* Create a instance of AppLocaleDetails.
@@ -111,6 +113,12 @@ public class AppLocaleDetails extends SettingsPreferenceFragment {
public void onResume() {
super.onResume();
refreshUi();
+ final Activity activity = getActivity();
+ if (mParentLocale != null) {
+ activity.setTitle(mParentLocale);
+ } else {
+ activity.setTitle(R.string.app_locale_picker_title);
+ }
}
private void refreshUi() {
@@ -215,4 +223,8 @@ public class AppLocaleDetails extends SettingsPreferenceFragment {
return LocaleHelper.getDisplayName(appLocale.stripExtensions(), appLocale, true);
}
}
+
+ public void setParentLocale(@Nullable String localeName) {
+ mParentLocale = localeName;
+ }
}
diff --git a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
index 11194ce92dc..43b5da28044 100644
--- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
+++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
@@ -75,14 +75,11 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
@VisibleForTesting
static final String RETRY_PREFERENCE_BUNDLE = "retry_preference_bundle";
private static final String BIOMETRICS_AUTH_REQUESTED = "biometrics_auth_requested";
- private static final String BIOMETRICS_AUTHENTICATED_SUCCESSFULLY =
- "biometrics_authenticated_successfully";
protected int mUserId;
protected long mGkPwHandle;
private boolean mConfirmCredential;
private boolean mBiometricsAuthenticationRequested;
- private boolean mBiometricsSuccessfullyAuthenticated;
@Nullable private FaceManager mFaceManager;
@Nullable private FingerprintManager mFingerprintManager;
// Do not finish() if choosing/confirming credential, showing fp/face settings, or launching
@@ -120,9 +117,6 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(getIntent());
}
- mBiometricsSuccessfullyAuthenticated = getIntent().getBooleanExtra(
- BIOMETRICS_AUTHENTICATED_SUCCESSFULLY, false);
-
if (savedInstanceState != null) {
mConfirmCredential = savedInstanceState.getBoolean(SAVE_STATE_CONFIRM_CREDETIAL);
mDoNotFinishActivity = savedInstanceState.getBoolean(DO_NOT_FINISH_ACTIVITY);
@@ -135,21 +129,12 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
}
mBiometricsAuthenticationRequested = savedInstanceState.getBoolean(
BIOMETRICS_AUTH_REQUESTED);
- mBiometricsSuccessfullyAuthenticated = savedInstanceState.getBoolean(
- BIOMETRICS_AUTHENTICATED_SUCCESSFULLY);
}
if (mGkPwHandle == 0L && !mConfirmCredential) {
mConfirmCredential = true;
launchChooseOrConfirmLock();
- } else if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(
- getActivity(), mBiometricsSuccessfullyAuthenticated,
- mBiometricsAuthenticationRequested, mUserId)) {
- mBiometricsAuthenticationRequested = true;
- Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST,
- mUserId, true /* hideBackground */);
}
-
updateUnlockPhonePreferenceSummary();
final Preference useInAppsPreference = findPreference(getUseInAppsPreferenceKey());
@@ -161,13 +146,6 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
@Override
public void onResume() {
super.onResume();
- if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
- mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested, mUserId)
- && mGkPwHandle != 0L) {
- mBiometricsAuthenticationRequested = true;
- Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST,
- mUserId, true /* hideBackground */);
- }
if (!mConfirmCredential) {
mDoNotFinishActivity = false;
}
@@ -204,9 +182,6 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
extras.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
extras.putInt(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, sensorId);
extras.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge);
- extras.putBoolean(
- BiometricEnrollBase.EXTRA_BIOMETRICS_AUTHENTICATED_SUCCESSFULLY,
- mBiometricsSuccessfullyAuthenticated);
onFaceOrFingerprintPreferenceTreeClick(preference);
} catch (IllegalStateException e) {
if (retry) {
@@ -236,9 +211,6 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
final Bundle extras = preference.getExtras();
extras.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
extras.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge);
- extras.putBoolean(
- BiometricEnrollBase.EXTRA_BIOMETRICS_AUTHENTICATED_SUCCESSFULLY,
- mBiometricsSuccessfullyAuthenticated);
onFaceOrFingerprintPreferenceTreeClick(preference);
} catch (IllegalStateException e) {
if (retry) {
@@ -323,8 +295,6 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
}
outState.putBoolean(BIOMETRICS_AUTH_REQUESTED,
mBiometricsAuthenticationRequested);
- outState.putBoolean(BIOMETRICS_AUTHENTICATED_SUCCESSFULLY,
- mBiometricsSuccessfullyAuthenticated);
}
@Override
@@ -342,6 +312,20 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
com.google.android.setupdesign.R.anim.sud_slide_next_out);
retryPreferenceKey(mRetryPreferenceKey, mRetryPreferenceExtra);
}
+ final Utils.BiometricStatus biometricAuthStatus =
+ Utils.requestBiometricAuthenticationForMandatoryBiometrics(
+ getActivity(),
+ mBiometricsAuthenticationRequested,
+ mUserId);
+ if (biometricAuthStatus == Utils.BiometricStatus.OK) {
+ mBiometricsAuthenticationRequested = true;
+ Utils.launchBiometricPromptForMandatoryBiometrics(this,
+ BIOMETRIC_AUTH_REQUEST,
+ mUserId, true /* hideBackground */);
+ } else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
+ finish();
+ return;
+ }
} else {
Log.d(getLogTag(), "Data null or GK PW missing.");
finish();
@@ -354,9 +338,7 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
mRetryPreferenceExtra = null;
} else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
mBiometricsAuthenticationRequested = false;
- if (resultCode == RESULT_OK) {
- mBiometricsSuccessfullyAuthenticated = true;
- } else {
+ if (resultCode != RESULT_OK) {
finish();
}
}
diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java
index bcd523142be..d42b570b30d 100644
--- a/src/com/android/settings/biometrics/face/FaceSettings.java
+++ b/src/com/android/settings/biometrics/face/FaceSettings.java
@@ -23,7 +23,6 @@ import static com.android.settings.Utils.isPrivateProfile;
import static com.android.settings.biometrics.BiometricEnrollBase.BIOMETRIC_AUTH_REQUEST;
import static com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST;
import static com.android.settings.biometrics.BiometricEnrollBase.ENROLL_REQUEST;
-import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_BIOMETRICS_AUTHENTICATED_SUCCESSFULLY;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT;
@@ -98,7 +97,6 @@ public class FaceSettings extends DashboardFragment {
private boolean mConfirmingPassword;
private boolean mBiometricsAuthenticationRequested;
- private boolean mBiometricsSuccessfullyAuthenticated;
private final FaceSettingsRemoveButtonPreferenceController.Listener mRemovalListener = () -> {
@@ -150,8 +148,6 @@ public class FaceSettings extends DashboardFragment {
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putByteArray(KEY_TOKEN, mToken);
- outState.putBoolean(KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED,
- mBiometricsSuccessfullyAuthenticated);
}
@Override
@@ -171,8 +167,6 @@ public class FaceSettings extends DashboardFragment {
mToken = getIntent().getByteArrayExtra(KEY_TOKEN);
mSensorId = getIntent().getIntExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, -1);
mChallenge = getIntent().getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, 0L);
- mBiometricsSuccessfullyAuthenticated = getIntent().getBooleanExtra(
- EXTRA_BIOMETRICS_AUTHENTICATED_SUCCESSFULLY, false);
mUserId = getActivity().getIntent().getIntExtra(
Intent.EXTRA_USER_ID, UserHandle.myUserId());
@@ -241,8 +235,6 @@ public class FaceSettings extends DashboardFragment {
if (savedInstanceState != null) {
mToken = savedInstanceState.getByteArray(KEY_TOKEN);
- mBiometricsSuccessfullyAuthenticated = savedInstanceState.getBoolean(
- KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED);
}
}
@@ -288,12 +280,6 @@ public class FaceSettings extends DashboardFragment {
Log.e(TAG, "Password not set");
finish();
}
- } else if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
- mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested,
- mUserId)) {
- mBiometricsAuthenticationRequested = true;
- Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST,
- mUserId, true /* hideBackground */);
} else {
mAttentionController.setToken(mToken);
mEnrollController.setToken(mToken);
@@ -330,6 +316,17 @@ public class FaceSettings extends DashboardFragment {
final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId);
mEnrollButton.setVisible(!hasEnrolled);
mRemoveButton.setVisible(hasEnrolled);
+ final Utils.BiometricStatus biometricAuthStatus =
+ Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
+ mBiometricsAuthenticationRequested,
+ mUserId);
+ if (biometricAuthStatus == Utils.BiometricStatus.OK) {
+ Utils.launchBiometricPromptForMandatoryBiometrics(this,
+ BIOMETRIC_AUTH_REQUEST,
+ mUserId, true /* hideBackground */);
+ } else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
+ finish();
+ }
}
} else if (requestCode == ENROLL_REQUEST) {
if (resultCode == RESULT_TIMEOUT) {
@@ -338,9 +335,7 @@ public class FaceSettings extends DashboardFragment {
}
} else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
mBiometricsAuthenticationRequested = false;
- if (resultCode == RESULT_OK) {
- mBiometricsSuccessfullyAuthenticated = true;
- } else {
+ if (resultCode != RESULT_OK) {
finish();
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
index 526ae8f6ed0..125691fbf1c 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
@@ -239,8 +239,6 @@ public class FingerprintSettings extends SubSettings {
"security_settings_fingerprint_footer";
private static final String KEY_BIOMETRICS_AUTHENTICATION_REQUESTED =
"biometrics_authentication_requested";
- private static final String KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED =
- "biometrics_successfully_authenticated";
private static final int MSG_REFRESH_FINGERPRINT_TEMPLATES = 1000;
private static final int MSG_FINGER_AUTH_SUCCESS = 1001;
@@ -276,7 +274,6 @@ public class FingerprintSettings extends SubSettings {
private byte[] mToken;
private boolean mLaunchedConfirm;
private boolean mBiometricsAuthenticationRequested;
- private boolean mBiometricsSuccessfullyAuthenticated;
private boolean mHasFirstEnrolled = true;
private Drawable mHighlightDrawable;
private int mUserId;
@@ -451,8 +448,6 @@ public class FingerprintSettings extends SubSettings {
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
mChallenge = activity.getIntent()
.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L);
- mBiometricsSuccessfullyAuthenticated = getIntent().getBooleanExtra(
- BiometricEnrollBase.EXTRA_BIOMETRICS_AUTHENTICATED_SUCCESSFULLY, false);
mAuthenticateSidecar = (FingerprintAuthenticateSidecar)
getFragmentManager().findFragmentByTag(TAG_AUTHENTICATE_SIDECAR);
@@ -494,8 +489,6 @@ public class FingerprintSettings extends SubSettings {
mIsEnrolling = savedInstanceState.getBoolean(KEY_IS_ENROLLING, mIsEnrolling);
mHasFirstEnrolled = savedInstanceState.getBoolean(KEY_HAS_FIRST_ENROLLED,
mHasFirstEnrolled);
- mBiometricsSuccessfullyAuthenticated = savedInstanceState.getBoolean(
- KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED);
mBiometricsAuthenticationRequested = savedInstanceState.getBoolean(
KEY_BIOMETRICS_AUTHENTICATION_REQUESTED);
}
@@ -506,12 +499,6 @@ public class FingerprintSettings extends SubSettings {
if (mToken == null) {
mLaunchedConfirm = true;
launchChooseOrConfirmLock();
- } else if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
- mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested,
- mUserId)) {
- mBiometricsAuthenticationRequested = true;
- Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST,
- mUserId, true /* hideBackground */);
} else if (!mHasFirstEnrolled) {
mIsEnrolling = true;
addFirstFingerprint(null);
@@ -801,14 +788,6 @@ public class FingerprintSettings extends SubSettings {
mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider()
.getUdfpsEnrollCalibrator(getActivity().getApplicationContext(), null, null);
-
- if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
- mBiometricsSuccessfullyAuthenticated, mBiometricsAuthenticationRequested,
- mUserId)) {
- mBiometricsAuthenticationRequested = true;
- Utils.launchBiometricPromptForMandatoryBiometrics(this,
- BIOMETRIC_AUTH_REQUEST, mUserId, true /* hideBackground */);
- }
}
private void updatePreferences() {
@@ -858,8 +837,6 @@ public class FingerprintSettings extends SubSettings {
outState.putBoolean(KEY_HAS_FIRST_ENROLLED, mHasFirstEnrolled);
outState.putBoolean(KEY_BIOMETRICS_AUTHENTICATION_REQUESTED,
mBiometricsAuthenticationRequested);
- outState.putBoolean(KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED,
- mBiometricsSuccessfullyAuthenticated);
}
@Override
@@ -1023,6 +1000,18 @@ public class FingerprintSettings extends SubSettings {
updateAddPreference();
});
}
+ final Utils.BiometricStatus biometricAuthStatus =
+ Utils.requestBiometricAuthenticationForMandatoryBiometrics(
+ getActivity(),
+ mBiometricsAuthenticationRequested,
+ mUserId);
+ if (biometricAuthStatus == Utils.BiometricStatus.OK) {
+ Utils.launchBiometricPromptForMandatoryBiometrics(this,
+ BIOMETRIC_AUTH_REQUEST,
+ mUserId, true /* hideBackground */);
+ } else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
+ finish();
+ }
} else {
Log.d(TAG, "Data null or GK PW missing");
finish();
@@ -1075,9 +1064,7 @@ public class FingerprintSettings extends SubSettings {
updateAddPreference();
} else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
mBiometricsAuthenticationRequested = false;
- if (resultCode == RESULT_OK) {
- mBiometricsSuccessfullyAuthenticated = true;
- } else {
+ if (resultCode != RESULT_OK) {
finish();
}
}
diff --git a/src/com/android/settings/bluetooth/BlockingPrefWithSliceController.java b/src/com/android/settings/bluetooth/BlockingPrefWithSliceController.java
index 0690186b972..442acd2dd3f 100644
--- a/src/com/android/settings/bluetooth/BlockingPrefWithSliceController.java
+++ b/src/com/android/settings/bluetooth/BlockingPrefWithSliceController.java
@@ -101,7 +101,8 @@ public class BlockingPrefWithSliceController extends BasePreferenceController im
return mUri != null ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
- public void setSliceUri(Uri uri) {
+ /** Sets Slice uri for the preference. */
+ public void setSliceUri(@Nullable Uri uri) {
mUri = uri;
mLiveData = SliceLiveData.fromUri(mContext, mUri, (int type, Throwable source) -> {
Log.w(TAG, "Slice may be null. uri = " + uri + ", error = " + type);
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
index 4ff71360a49..398edb6b991 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
@@ -39,8 +39,8 @@ import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.flags.Flags;
import com.android.settingslib.utils.ThreadUtils;
@@ -299,57 +299,14 @@ public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsCont
+ " profiles: "
+ mCachedDevice.getProfiles());
- AudioDeviceAttributes saDevice = null;
- for (LocalBluetoothProfile profile : mCachedDevice.getProfiles()) {
- // pick first enabled profile that is compatible with spatial audio
- if (SA_PROFILES.contains(profile.getProfileId())
- && profile.isEnabled(mCachedDevice.getDevice())) {
- switch (profile.getProfileId()) {
- case BluetoothProfile.A2DP:
- saDevice =
- new AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
- mCachedDevice.getAddress());
- break;
- case BluetoothProfile.LE_AUDIO:
- if (mAudioManager.getBluetoothAudioDeviceCategory(
- mCachedDevice.getAddress())
- == AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER) {
- saDevice =
- new AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLE_SPEAKER,
- mCachedDevice.getAddress());
- } else {
- saDevice =
- new AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_BLE_HEADSET,
- mCachedDevice.getAddress());
- }
-
- break;
- case BluetoothProfile.HEARING_AID:
- saDevice =
- new AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- AudioDeviceInfo.TYPE_HEARING_AID,
- mCachedDevice.getAddress());
- break;
- default:
- Log.i(
- TAG,
- "unrecognized profile for spatial audio: "
- + profile.getProfileId());
- break;
- }
- break;
- }
- }
- mAudioDevice = null;
+ AudioDeviceAttributes saDevice =
+ BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+ mCachedDevice,
+ mAudioManager.getBluetoothAudioDeviceCategory(mCachedDevice.getAddress()));
if (saDevice != null && mSpatializer.isAvailableForDevice(saDevice)) {
mAudioDevice = saDevice;
+ } else {
+ mAudioDevice = null;
}
Log.d(
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
index ccf38ed2835..bd762a1ef11 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -43,10 +43,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
+import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter;
import com.android.settings.connecteddevice.stylus.StylusDevicesController;
import com.android.settings.core.SettingsUIDeviceConfig;
import com.android.settings.dashboard.RestrictedDashboardFragment;
@@ -60,9 +62,11 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment {
public static final String KEY_DEVICE_ADDRESS = "device_address";
@@ -98,6 +102,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
@VisibleForTesting
CachedBluetoothDevice mCachedDevice;
BluetoothAdapter mBluetoothAdapter;
+ @VisibleForTesting
+ DeviceDetailsFragmentFormatter mFormatter;
@Nullable
InputDevice mInputDevice;
@@ -214,18 +220,29 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
finish();
return;
}
- use(AdvancedBluetoothDetailsHeaderController.class).init(mCachedDevice, this);
- use(LeAudioBluetoothDetailsHeaderController.class).init(mCachedDevice, mManager, this);
- use(KeyboardSettingsPreferenceController.class).init(mCachedDevice);
+ getController(
+ AdvancedBluetoothDetailsHeaderController.class,
+ controller -> controller.init(mCachedDevice, this));
+ getController(
+ LeAudioBluetoothDetailsHeaderController.class,
+ controller -> controller.init(mCachedDevice, mManager, this));
+ getController(
+ KeyboardSettingsPreferenceController.class,
+ controller -> controller.init(mCachedDevice));
final BluetoothFeatureProvider featureProvider =
FeatureFactory.getFeatureFactory().getBluetoothFeatureProvider();
final boolean sliceEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
SettingsUIDeviceConfig.BT_SLICE_SETTINGS_ENABLED, true);
- use(BlockingPrefWithSliceController.class).setSliceUri(sliceEnabled
- ? featureProvider.getBluetoothDeviceSettingsUri(mCachedDevice.getDevice())
- : null);
+ getController(
+ BlockingPrefWithSliceController.class,
+ controller ->
+ controller.setSliceUri(
+ sliceEnabled
+ ? featureProvider.getBluetoothDeviceSettingsUri(
+ mCachedDevice.getDevice())
+ : null));
mManager.getEventManager().registerCallback(mBluetoothCallback);
mBluetoothAdapter.addOnMetadataChangedListener(
@@ -257,21 +274,35 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
}
}
mExtraControlUriLoaded |= controlUri != null;
- final SlicePreferenceController slicePreferenceController = use(
- SlicePreferenceController.class);
- slicePreferenceController.setSliceUri(sliceEnabled ? controlUri : null);
- slicePreferenceController.onStart();
- slicePreferenceController.displayPreference(getPreferenceScreen());
+
+ Uri finalControlUri = controlUri;
+ getController(SlicePreferenceController.class, controller -> {
+ controller.setSliceUri(sliceEnabled ? finalControlUri : null);
+ controller.onStart();
+ controller.displayPreference(getPreferenceScreen());
+ });
+
// Temporarily fix the issue that the page will be automatically scrolled to a wrong
// position when entering the page. This will make sure the bluetooth header is shown on top
// of the page.
- use(LeAudioBluetoothDetailsHeaderController.class).displayPreference(
- getPreferenceScreen());
- use(AdvancedBluetoothDetailsHeaderController.class).displayPreference(
- getPreferenceScreen());
- use(BluetoothDetailsHeaderController.class).displayPreference(
- getPreferenceScreen());
+ getController(
+ LeAudioBluetoothDetailsHeaderController.class,
+ controller -> controller.displayPreference(getPreferenceScreen()));
+ getController(
+ AdvancedBluetoothDetailsHeaderController.class,
+ controller -> controller.displayPreference(getPreferenceScreen()));
+ getController(
+ BluetoothDetailsHeaderController.class,
+ controller -> controller.displayPreference(getPreferenceScreen()));
+ }
+
+ protected void getController(Class clazz,
+ Consumer action) {
+ T controller = use(clazz);
+ if (controller != null) {
+ action.accept(controller);
+ }
}
private final ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener =
@@ -308,6 +339,14 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
return view;
}
+ @Override
+ public void onCreatePreferences(@NonNull Bundle savedInstanceState, @NonNull String rootKey) {
+ super.onCreatePreferences(savedInstanceState, rootKey);
+ if (Flags.enableBluetoothDeviceDetailsPolish()) {
+ mFormatter.updateLayout();
+ }
+ }
+
@Override
public void onResume() {
super.onResume();
@@ -358,8 +397,30 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
return super.onOptionsItemSelected(menuItem);
}
+ @Override
+ protected void addPreferenceController(AbstractPreferenceController controller) {
+ if (Flags.enableBluetoothDeviceDetailsPolish()) {
+ List keys = mFormatter.getVisiblePreferenceKeysForMainPage();
+ Lifecycle lifecycle = getSettingsLifecycle();
+ if (keys == null || keys.contains(controller.getPreferenceKey())) {
+ super.addPreferenceController(controller);
+ } else if (controller instanceof LifecycleObserver) {
+ lifecycle.removeObserver((LifecycleObserver) controller);
+ }
+ } else {
+ super.addPreferenceController(controller);
+ }
+ }
+
@Override
protected List createPreferenceControllers(Context context) {
+ if (Flags.enableBluetoothDeviceDetailsPolish()) {
+ mFormatter =
+ FeatureFactory.getFeatureFactory()
+ .getBluetoothFeatureProvider()
+ .getDeviceDetailsFragmentFormatter(
+ requireContext(), this, mBluetoothAdapter, mCachedDevice);
+ }
ArrayList controllers = new ArrayList<>();
if (mCachedDevice != null) {
diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
index 1751082a45f..be0f6f36b6c 100644
--- a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
+++ b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
@@ -16,15 +16,23 @@
package com.android.settings.bluetooth;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
+import android.media.AudioManager;
import android.media.Spatializer;
import android.net.Uri;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleCoroutineScope;
import androidx.preference.Preference;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor;
+import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository;
import java.util.List;
import java.util.Set;
@@ -84,4 +92,26 @@ public interface BluetoothFeatureProvider {
*/
Set getInvisibleProfilePreferenceKeys(
Context context, BluetoothDevice bluetoothDevice);
+
+ /** Gets DeviceSettingRepository. */
+ @NonNull
+ DeviceSettingRepository getDeviceSettingRepository(
+ @NonNull Context context,
+ @NonNull BluetoothAdapter bluetoothAdapter,
+ @NonNull LifecycleCoroutineScope scope);
+
+ /** Gets spatial audio interactor. */
+ @NonNull
+ SpatialAudioInteractor getSpatialAudioInteractor(
+ @NonNull Context context,
+ @NonNull AudioManager audioManager,
+ @NonNull LifecycleCoroutineScope scope);
+
+ /** Gets device details fragment layout formatter. */
+ @NonNull
+ DeviceDetailsFragmentFormatter getDeviceDetailsFragmentFormatter(
+ @NonNull Context context,
+ @NonNull SettingsPreferenceFragment fragment,
+ @NonNull BluetoothAdapter bluetoothAdapter,
+ @NonNull CachedBluetoothDevice cachedDevice);
}
diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java
deleted file mode 100644
index 2d4ac496d49..00000000000
--- a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 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.bluetooth;
-
-import android.bluetooth.BluetoothDevice;
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.Spatializer;
-import android.net.Uri;
-
-import androidx.preference.Preference;
-
-import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * Impl of {@link BluetoothFeatureProvider}
- */
-public class BluetoothFeatureProviderImpl implements BluetoothFeatureProvider {
-
- @Override
- public Uri getBluetoothDeviceSettingsUri(BluetoothDevice bluetoothDevice) {
- final byte[] uriByte = bluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI);
- return uriByte == null ? null : Uri.parse(new String(uriByte));
- }
-
- @Override
- public String getBluetoothDeviceControlUri(BluetoothDevice bluetoothDevice) {
- return BluetoothUtils.getControlUriMetaData(bluetoothDevice);
- }
-
- @Override
- public List getRelatedTools() {
- return null;
- }
-
- @Override
- public Spatializer getSpatializer(Context context) {
- AudioManager audioManager = context.getSystemService(AudioManager.class);
- return audioManager.getSpatializer();
- }
-
- @Override
- public List getBluetoothExtraOptions(Context context,
- CachedBluetoothDevice device) {
- return ImmutableList.of();
- }
-
- @Override
- public Set getInvisibleProfilePreferenceKeys(
- Context context, BluetoothDevice bluetoothDevice) {
- return ImmutableSet.of();
- }
-}
diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt
new file mode 100644
index 00000000000..3a549c6b2de
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2018 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.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.content.ComponentName
+import android.content.Context
+import android.media.AudioManager
+import android.media.Spatializer
+import android.net.Uri
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.preference.Preference
+import com.android.settings.SettingsPreferenceFragment
+import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
+import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractorImpl
+import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter
+import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatterImpl
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository
+import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepositoryImpl
+import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.google.common.collect.ImmutableList
+import com.google.common.collect.ImmutableSet
+import kotlinx.coroutines.Dispatchers
+
+/** Impl of [BluetoothFeatureProvider] */
+open class BluetoothFeatureProviderImpl : BluetoothFeatureProvider {
+ override fun getBluetoothDeviceSettingsUri(bluetoothDevice: BluetoothDevice): Uri? {
+ val uriByte = bluetoothDevice.getMetadata(BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI)
+ return uriByte?.let { Uri.parse(String(it)) }
+ }
+
+ override fun getBluetoothDeviceControlUri(bluetoothDevice: BluetoothDevice): String? {
+ return BluetoothUtils.getControlUriMetaData(bluetoothDevice)
+ }
+
+ override fun getRelatedTools(): List? {
+ return null
+ }
+
+ override fun getSpatializer(context: Context): Spatializer? {
+ val audioManager = context.getSystemService(AudioManager::class.java)
+ return audioManager.spatializer
+ }
+
+ override fun getBluetoothExtraOptions(
+ context: Context,
+ device: CachedBluetoothDevice
+ ): List? {
+ return ImmutableList.of()
+ }
+
+ override fun getInvisibleProfilePreferenceKeys(
+ context: Context,
+ bluetoothDevice: BluetoothDevice
+ ): Set {
+ return ImmutableSet.of()
+ }
+
+ override fun getDeviceSettingRepository(
+ context: Context,
+ bluetoothAdapter: BluetoothAdapter,
+ scope: LifecycleCoroutineScope
+ ): DeviceSettingRepository =
+ DeviceSettingRepositoryImpl(context, bluetoothAdapter, scope, Dispatchers.IO)
+
+ override fun getSpatialAudioInteractor(
+ context: Context,
+ audioManager: AudioManager,
+ scope: LifecycleCoroutineScope
+ ): SpatialAudioInteractor {
+ return SpatialAudioInteractorImpl(
+ context, audioManager,
+ SpatializerInteractor(
+ SpatializerRepositoryImpl(
+ audioManager.spatializer,
+ Dispatchers.IO
+ )
+ ), scope, Dispatchers.IO)
+ }
+
+ override fun getDeviceDetailsFragmentFormatter(
+ context: Context,
+ fragment: SettingsPreferenceFragment,
+ bluetoothAdapter: BluetoothAdapter,
+ cachedDevice: CachedBluetoothDevice
+ ): DeviceDetailsFragmentFormatter {
+ return DeviceDetailsFragmentFormatterImpl(context, fragment, bluetoothAdapter, cachedDevice)
+ }
+}
diff --git a/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt b/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt
new file mode 100644
index 00000000000..6b72b53aa3f
--- /dev/null
+++ b/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 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.bluetooth.domain.interactor
+
+import android.content.Context
+import android.media.AudioManager
+import android.util.Log
+import com.android.settings.R
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Provides device setting for spatial audio. */
+interface SpatialAudioInteractor {
+ /** Gets device setting for spatial audio */
+ fun getDeviceSetting(
+ cachedDevice: CachedBluetoothDevice,
+ ): Flow
+}
+
+class SpatialAudioInteractorImpl(
+ private val context: Context,
+ private val audioManager: AudioManager,
+ private val spatializerInteractor: SpatializerInteractor,
+ private val coroutineScope: CoroutineScope,
+ private val backgroundCoroutineContext: CoroutineContext,
+) : SpatialAudioInteractor {
+ private val spatialAudioOffToggle =
+ ToggleModel(
+ context.getString(R.string.spatial_audio_multi_toggle_off),
+ DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio_off))
+ private val spatialAudioOnToggle =
+ ToggleModel(
+ context.getString(R.string.spatial_audio_multi_toggle_on),
+ DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio))
+ private val headTrackingOnToggle =
+ ToggleModel(
+ context.getString(R.string.spatial_audio_multi_toggle_head_tracking_on),
+ DeviceSettingIcon.ResourceIcon(R.drawable.ic_head_tracking))
+ private val changes = MutableSharedFlow()
+
+ override fun getDeviceSetting(
+ cachedDevice: CachedBluetoothDevice,
+ ): Flow =
+ changes
+ .onStart { emit(Unit) }
+ .map { getSpatialAudioDeviceSettingModel(cachedDevice) }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = null)
+
+ private suspend fun getSpatialAudioDeviceSettingModel(
+ cachedDevice: CachedBluetoothDevice,
+ ): DeviceSettingModel? {
+ // TODO(b/343317785): use audio repository instead of calling AudioManager directly.
+ Log.i(TAG, "CachedDevice: $cachedDevice profiles: ${cachedDevice.profiles}")
+ val attributes =
+ BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+ cachedDevice, audioManager.getBluetoothAudioDeviceCategory(cachedDevice.address))
+ ?: run {
+ Log.i(TAG, "No audio profiles in cachedDevice: ${cachedDevice.address}.")
+ return null
+ }
+
+ Log.i(TAG, "Audio device attributes for ${cachedDevice.address}: $attributes.")
+ val spatialAudioAvailable = spatializerInteractor.isSpatialAudioAvailable(attributes)
+ if (!spatialAudioAvailable) {
+ Log.i(TAG, "Spatial audio is not available for ${cachedDevice.address}")
+ return null
+ }
+ val headTrackingAvailable =
+ spatialAudioAvailable && spatializerInteractor.isHeadTrackingAvailable(attributes)
+ val toggles =
+ if (headTrackingAvailable) {
+ listOf(spatialAudioOffToggle, spatialAudioOnToggle, headTrackingOnToggle)
+ } else {
+ listOf(spatialAudioOffToggle, spatialAudioOnToggle)
+ }
+ val spatialAudioEnabled = spatializerInteractor.isSpatialAudioEnabled(attributes)
+ val headTrackingEnabled =
+ spatialAudioEnabled && spatializerInteractor.isHeadTrackingEnabled(attributes)
+
+ val activeIndex =
+ when {
+ headTrackingEnabled -> INDEX_HEAD_TRACKING_ENABLED
+ spatialAudioEnabled -> INDEX_SPATIAL_AUDIO_ON
+ else -> INDEX_SPATIAL_AUDIO_OFF
+ }
+ Log.i(
+ TAG,
+ "Head tracking available: $headTrackingAvailable, " +
+ "spatial audio enabled: $spatialAudioEnabled, " +
+ "head tracking enabled: $headTrackingEnabled")
+ return DeviceSettingModel.MultiTogglePreference(
+ cachedDevice = cachedDevice,
+ id = DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE,
+ title = context.getString(R.string.spatial_audio_multi_toggle_title),
+ toggles = toggles,
+ isActive = spatialAudioEnabled,
+ state = DeviceSettingStateModel.MultiTogglePreferenceState(activeIndex),
+ isAllowedChangingState = true,
+ updateState = { newState ->
+ coroutineScope.launch(backgroundCoroutineContext) {
+ Log.i(TAG, "Update spatial audio state: $newState")
+ when (newState.selectedIndex) {
+ INDEX_SPATIAL_AUDIO_OFF -> {
+ spatializerInteractor.setSpatialAudioEnabled(attributes, false)
+ }
+ INDEX_SPATIAL_AUDIO_ON -> {
+ spatializerInteractor.setSpatialAudioEnabled(attributes, true)
+ spatializerInteractor.setHeadTrackingEnabled(attributes, false)
+ }
+ INDEX_HEAD_TRACKING_ENABLED -> {
+ spatializerInteractor.setSpatialAudioEnabled(attributes, true)
+ spatializerInteractor.setHeadTrackingEnabled(attributes, true)
+ }
+ }
+ changes.emit(Unit)
+ }
+ })
+ }
+
+ companion object {
+ private const val TAG = "SpatialAudioInteractorImpl"
+ private const val INDEX_SPATIAL_AUDIO_OFF = 0
+ private const val INDEX_SPATIAL_AUDIO_ON = 1
+ private const val INDEX_HEAD_TRACKING_ENABLED = 2
+ }
+}
diff --git a/src/com/android/settings/bluetooth/ui/composable/Icon.kt b/src/com/android/settings/bluetooth/ui/composable/Icon.kt
new file mode 100644
index 00000000000..676bd14fcca
--- /dev/null
+++ b/src/com/android/settings/bluetooth/ui/composable/Icon.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 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.bluetooth.ui.composable
+
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.painterResource
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
+
+@Composable
+fun Icon(
+ icon: DeviceSettingIcon,
+ modifier: Modifier = Modifier,
+ tint: Color = LocalContentColor.current,
+) {
+ when (icon) {
+ is DeviceSettingIcon.BitmapIcon ->
+ androidx.compose.material3.Icon(
+ icon.bitmap.asImageBitmap(),
+ contentDescription = null,
+ modifier = modifier,
+ tint = LocalContentColor.current)
+ is DeviceSettingIcon.ResourceIcon ->
+ androidx.compose.material3.Icon(
+ painterResource(icon.resId),
+ contentDescription = null,
+ modifier = modifier,
+ tint = tint)
+ else -> {}
+ }
+}
diff --git a/src/com/android/settings/bluetooth/ui/MultiTogglePreferenceGroup.kt b/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt
similarity index 80%
rename from src/com/android/settings/bluetooth/ui/MultiTogglePreferenceGroup.kt
rename to src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt
index e4ca00d47a9..8fe3c255d34 100644
--- a/src/com/android/settings/bluetooth/ui/MultiTogglePreferenceGroup.kt
+++ b/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settings.bluetooth.ui
+package com.android.settings.bluetooth.ui.composable
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
@@ -51,7 +51,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.boundsInParent
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
@@ -67,6 +66,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.DialogProperties
import com.android.settings.R
+import com.android.settings.bluetooth.ui.composable.Icon as DeviceSettingComposeIcon
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
import com.android.settingslib.spa.framework.theme.SettingsDimension
@@ -97,35 +97,29 @@ fun MultiTogglePreferenceGroup(
Surface(
modifier = Modifier.height(64.dp),
shape = RoundedCornerShape(28.dp),
- color = MaterialTheme.colorScheme.surface
- ) {
- Button(
- modifier =
- Modifier.fillMaxSize().padding(8.dp).semantics {
- role = Role.Switch
- toggleableState =
- if (preferenceModel.isActive) {
- ToggleableState.On
- } else {
- ToggleableState.Off
- }
- contentDescription = preferenceModel.title
- },
- onClick = { settingIdForPopUp = preferenceModel.id },
- shape = RoundedCornerShape(20.dp),
- colors = getButtonColors(preferenceModel.isActive),
- contentPadding = PaddingValues(0.dp)
- ) {
- Icon(
- preferenceModel.toggles[preferenceModel.state.selectedIndex]
- .icon
- .asImageBitmap(),
- contentDescription = null,
- modifier = Modifier.size(24.dp),
- tint = LocalContentColor.current
- )
+ color = MaterialTheme.colorScheme.surface) {
+ Button(
+ modifier =
+ Modifier.fillMaxSize().padding(8.dp).semantics {
+ role = Role.Switch
+ toggleableState =
+ if (preferenceModel.isActive) {
+ ToggleableState.On
+ } else {
+ ToggleableState.Off
+ }
+ contentDescription = preferenceModel.title
+ },
+ onClick = { settingIdForPopUp = preferenceModel.id },
+ shape = RoundedCornerShape(20.dp),
+ colors = getButtonColors(preferenceModel.isActive),
+ contentPadding = PaddingValues(0.dp)) {
+ DeviceSettingComposeIcon(
+ preferenceModel.toggles[preferenceModel.state.selectedIndex]
+ .icon,
+ modifier = Modifier.size(24.dp))
+ }
}
- }
}
Row { Text(text = preferenceModel.title, fontSize = 12.sp) }
}
@@ -173,8 +167,7 @@ private fun dialog(
Icon(
painterResource(id = R.drawable.ic_close),
null,
- tint = MaterialTheme.colorScheme.inverseSurface
- )
+ tint = MaterialTheme.colorScheme.inverseSurface)
}
Box(modifier = Modifier.padding(horizontal = 8.dp, vertical = 20.dp)) {
dialogContent(multiTogglePreference)
@@ -182,8 +175,7 @@ private fun dialog(
}
},
)
- }
- )
+ })
}
@Composable
@@ -208,9 +200,7 @@ private fun dialogContent(multiTogglePreference: DeviceSettingModel.MultiToggleP
Modifier.fillMaxWidth()
.height(64.dp)
.background(
- MaterialTheme.colorScheme.surface,
- shape = RoundedCornerShape(28.dp)
- ),
+ MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(28.dp)),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly,
) {
@@ -224,9 +214,7 @@ private fun dialogContent(multiTogglePreference: DeviceSettingModel.MultiToggleP
.width(selectedRect!!.width.toDp())
.background(
MaterialTheme.colorScheme.tertiaryContainer,
- shape = RoundedCornerShape(20.dp)
- )
- )
+ shape = RoundedCornerShape(20.dp)))
}
}
Row {
@@ -238,9 +226,7 @@ private fun dialogContent(multiTogglePreference: DeviceSettingModel.MultiToggleP
.padding(horizontal = 8.dp)
.height(48.dp)
.background(
- Color.Transparent,
- shape = RoundedCornerShape(28.dp)
- )
+ Color.Transparent, shape = RoundedCornerShape(28.dp))
.onGloballyPositioned { layoutCoordinates ->
if (selected) {
selectedRect = layoutCoordinates.boundsInParent()
@@ -252,22 +238,16 @@ private fun dialogContent(multiTogglePreference: DeviceSettingModel.MultiToggleP
Button(
onClick = {
multiTogglePreference.updateState(
- DeviceSettingStateModel.MultiTogglePreferenceState(idx)
- )
+ DeviceSettingStateModel.MultiTogglePreferenceState(idx))
},
modifier = Modifier.fillMaxSize(),
colors =
ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
- contentColor = LocalContentColor.current
- ),
+ contentColor = LocalContentColor.current),
) {
- Icon(
- bitmap = toggle.icon.asImageBitmap(),
- null,
- modifier = Modifier.size(24.dp),
- tint = LocalContentColor.current
- )
+ DeviceSettingComposeIcon(
+ toggle.icon, modifier = Modifier.size(24.dp))
}
}
}
@@ -285,8 +265,7 @@ private fun dialogContent(multiTogglePreference: DeviceSettingModel.MultiToggleP
text = toggle.label,
fontSize = 12.sp,
textAlign = TextAlign.Center,
- modifier = Modifier.weight(1f).padding(horizontal = 8.dp)
- )
+ modifier = Modifier.weight(1f).padding(horizontal = 8.dp))
}
}
}
diff --git a/src/com/android/settings/bluetooth/ui/layout/DeviceSettingLayout.kt b/src/com/android/settings/bluetooth/ui/layout/DeviceSettingLayout.kt
new file mode 100644
index 00000000000..87e2e8b4962
--- /dev/null
+++ b/src/com/android/settings/bluetooth/ui/layout/DeviceSettingLayout.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 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.bluetooth.ui.layout
+
+import kotlinx.coroutines.flow.Flow
+
+/** Represent the layout of device settings. */
+data class DeviceSettingLayout(val rows: List)
+
+/** Represent a row in the layout. */
+data class DeviceSettingLayoutRow(val settingIds: Flow>)
diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
new file mode 100644
index 00000000000..b75579dfa0d
--- /dev/null
+++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2024 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.bluetooth.ui.view
+
+import android.bluetooth.BluetoothAdapter
+import android.content.Context
+import android.media.AudioManager
+import android.util.Log
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.preference.Preference
+import com.android.settings.SettingsPreferenceFragment
+import com.android.settings.bluetooth.ui.composable.Icon
+import com.android.settings.bluetooth.ui.composable.MultiTogglePreferenceGroup
+import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
+import com.android.settings.bluetooth.ui.viewmodel.BluetoothDeviceDetailsViewModel
+import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
+import com.android.settings.spa.preference.ComposePreference
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import com.android.settingslib.spa.widget.preference.Preference as SpaPreference
+
+
+/** Handles device details fragment layout according to config. */
+interface DeviceDetailsFragmentFormatter {
+ /** Gets keys of visible preferences in built-in preference in xml. */
+ fun getVisiblePreferenceKeysForMainPage(): List?
+
+ /** Updates device details fragment layout. */
+ fun updateLayout()
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class DeviceDetailsFragmentFormatterImpl(
+ private val context: Context,
+ private val fragment: SettingsPreferenceFragment,
+ bluetoothAdapter: BluetoothAdapter,
+ private val cachedDevice: CachedBluetoothDevice
+) : DeviceDetailsFragmentFormatter {
+ private val repository =
+ featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository(
+ context, bluetoothAdapter, fragment.lifecycleScope)
+ private val spatialAudioInteractor =
+ featureFactory.bluetoothFeatureProvider.getSpatialAudioInteractor(
+ context, context.getSystemService(AudioManager::class.java), fragment.lifecycleScope)
+ private val viewModel: BluetoothDeviceDetailsViewModel =
+ ViewModelProvider(
+ fragment,
+ BluetoothDeviceDetailsViewModel.Factory(
+ repository,
+ spatialAudioInteractor,
+ cachedDevice,
+ ))
+ .get(BluetoothDeviceDetailsViewModel::class.java)
+
+ override fun getVisiblePreferenceKeysForMainPage(): List? = runBlocking {
+ viewModel
+ .getItems()
+ ?.filterIsInstance()
+ ?.mapNotNull { it.preferenceKey }
+ }
+
+ /** Updates bluetooth device details fragment layout. */
+ override fun updateLayout() = runBlocking {
+ val items = viewModel.getItems() ?: return@runBlocking
+ val layout = viewModel.getLayout() ?: return@runBlocking
+ val prefKeyToSettingId =
+ items
+ .filterIsInstance()
+ .associateBy({ it.preferenceKey }, { it.settingId })
+
+ val settingIdToXmlPreferences: MutableMap = HashMap()
+ for (i in 0 until fragment.preferenceScreen.preferenceCount) {
+ val pref = fragment.preferenceScreen.getPreference(i)
+ prefKeyToSettingId[pref.key]?.let { id -> settingIdToXmlPreferences[id] = pref }
+ }
+ fragment.preferenceScreen.removeAll()
+
+ for (row in items.indices) {
+ val settingId = items[row].settingId
+ if (settingIdToXmlPreferences.containsKey(settingId)) {
+ fragment.preferenceScreen.addPreference(
+ settingIdToXmlPreferences[settingId]!!.apply { order = row })
+ } else {
+ val pref =
+ ComposePreference(context)
+ .apply {
+ key = getPreferenceKey(settingId)
+ order = row
+ }
+ .also { pref -> pref.setContent { buildPreference(layout, row) } }
+ fragment.preferenceScreen.addPreference(pref)
+ }
+ }
+ }
+
+ @Composable
+ private fun buildPreference(layout: DeviceSettingLayout, row: Int) {
+ val contents by
+ remember(row) {
+ layout.rows[row].settingIds.flatMapLatest { settingIds ->
+ if (settingIds.isEmpty()) {
+ flowOf(emptyList())
+ } else {
+ combine(
+ settingIds.map { settingId ->
+ viewModel.getDeviceSetting(cachedDevice, settingId)
+ }) {
+ it.toList()
+ }
+ }
+ }
+ }
+ .collectAsStateWithLifecycle(initialValue = listOf())
+
+ val settings = contents
+ when (settings.size) {
+ 0 -> {}
+ 1 -> {
+ when (val setting = settings[0]) {
+ is DeviceSettingModel.ActionSwitchPreference -> {
+ buildActionSwitchPreference(setting)
+ }
+ is DeviceSettingModel.MultiTogglePreference -> {
+ buildMultiTogglePreference(listOf(setting))
+ }
+ null -> {}
+ else -> {
+ Log.w(TAG, "Unknown preference type ${setting.id}, skip.")
+ }
+ }
+ }
+ else -> {
+ if (!settings.all { it is DeviceSettingModel.MultiTogglePreference }) {
+ return
+ }
+ buildMultiTogglePreference(
+ settings.filterIsInstance())
+ }
+ }
+ }
+
+ @Composable
+ private fun buildMultiTogglePreference(prefs: List) {
+ MultiTogglePreferenceGroup(prefs)
+ }
+
+ @Composable
+ private fun buildActionSwitchPreference(model: DeviceSettingModel.ActionSwitchPreference) {
+ if (model.switchState != null) {
+ val switchPrefModel =
+ object : SwitchPreferenceModel {
+ override val title = model.title
+ override val summary = { model.summary ?: "" }
+ override val checked = { model.switchState?.checked }
+ override val onCheckedChange = { newChecked: Boolean ->
+ model.updateState?.invoke(
+ DeviceSettingStateModel.ActionSwitchPreferenceState(newChecked))
+ Unit
+ }
+ override val icon = @Composable { deviceSettingIcon(model) }
+ }
+ if (model.intent != null) {
+ TwoTargetSwitchPreference(switchPrefModel) { context.startActivity(model.intent) }
+ } else {
+ SwitchPreference(switchPrefModel)
+ }
+ } else {
+ SpaPreference(
+ object : PreferenceModel {
+ override val title = model.title
+ override val summary = { model.summary ?: "" }
+ override val onClick = {
+ model.intent?.let { context.startActivity(it) }
+ Unit
+ }
+ override val icon = @Composable { deviceSettingIcon(model) }
+ })
+ }
+ }
+
+ @Composable
+ private fun deviceSettingIcon(model: DeviceSettingModel.ActionSwitchPreference) {
+ model.icon?.let { icon ->
+ Icon(icon, modifier = Modifier.size(SettingsDimension.itemIconSize))
+ }
+ }
+
+ private fun getPreferenceKey(settingId: Int) = "DEVICE_SETTING_${settingId}"
+
+ companion object {
+ const val TAG = "DeviceDetailsFormatter"
+ }
+}
diff --git a/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt b/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt
new file mode 100644
index 00000000000..befff830da3
--- /dev/null
+++ b/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 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.bluetooth.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
+import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
+import com.android.settings.bluetooth.ui.layout.DeviceSettingLayoutRow
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
+import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+class BluetoothDeviceDetailsViewModel(
+ private val deviceSettingRepository: DeviceSettingRepository,
+ private val spatialAudioInteractor: SpatialAudioInteractor,
+ private val cachedDevice: CachedBluetoothDevice,
+) : ViewModel() {
+ private val items =
+ viewModelScope.async(Dispatchers.IO, start = CoroutineStart.LAZY) {
+ deviceSettingRepository.getDeviceSettingsConfig(cachedDevice)
+ }
+
+ suspend fun getItems(): List? = items.await()?.mainItems
+
+ fun getDeviceSetting(
+ cachedDevice: CachedBluetoothDevice,
+ @DeviceSettingId settingId: Int
+ ): Flow {
+ return when (settingId) {
+ DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE ->
+ spatialAudioInteractor.getDeviceSetting(cachedDevice)
+ else -> deviceSettingRepository.getDeviceSetting(cachedDevice, settingId)
+ }
+ }
+
+ suspend fun getLayout(): DeviceSettingLayout? {
+ val configItems = getItems() ?: return null
+ val idToDeviceSetting =
+ configItems
+ .filterIsInstance()
+ .associateBy({ it.settingId }, { getDeviceSetting(cachedDevice, it.settingId) })
+
+ val configDeviceSetting =
+ configItems.map { idToDeviceSetting[it.settingId] ?: flowOf(null) }
+ val positionToSettingIds =
+ combine(configDeviceSetting) { settings ->
+ val positionMapping = mutableMapOf>()
+ var multiToggleSettingIds: MutableList? = null
+ for (i in settings.indices) {
+ val configItem = configItems[i]
+ val setting = settings[i]
+ val isXmlPreference = configItem is DeviceSettingConfigItemModel.BuiltinItem
+ if (!isXmlPreference && setting == null) {
+ continue
+ }
+ if (setting !is DeviceSettingModel.MultiTogglePreference) {
+ multiToggleSettingIds = null
+ positionMapping[i] = listOf(configItem.settingId)
+ continue
+ }
+
+ if (multiToggleSettingIds != null) {
+ multiToggleSettingIds.add(setting.id)
+ } else {
+ multiToggleSettingIds = mutableListOf(setting.id)
+ positionMapping[i] = multiToggleSettingIds
+ }
+ }
+ positionMapping
+ }
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
+ return DeviceSettingLayout(
+ configItems.indices.map { idx ->
+ DeviceSettingLayoutRow(positionToSettingIds.map { it[idx] ?: emptyList() })
+ })
+ }
+
+ class Factory(
+ private val deviceSettingRepository: DeviceSettingRepository,
+ private val spatialAudioInteractor: SpatialAudioInteractor,
+ private val cachedDevice: CachedBluetoothDevice,
+ ) : ViewModelProvider.Factory {
+ override fun create(modelClass: Class): T {
+ @Suppress("UNCHECKED_CAST")
+ return BluetoothDeviceDetailsViewModel(
+ deviceSettingRepository, spatialAudioInteractor, cachedDevice)
+ as T
+ }
+ }
+
+ companion object {
+ private const val TAG = "BluetoothDeviceDetailsViewModel"
+ }
+}
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 666d24dc50c..0df822af185 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -365,12 +365,19 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
if (isChecked != developmentEnabledState) {
if (isChecked) {
final int userId = getContext().getUserId();
- if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getContext(),
- mIsBiometricsAuthenticated,
- false /* biometricsAuthenticationRequested */, userId)) {
+
+ final Utils.BiometricStatus biometricAuthStatus =
+ Utils.requestBiometricAuthenticationForMandatoryBiometrics(
+ getContext(),
+ mIsBiometricsAuthenticated,
+ userId);
+ if (biometricAuthStatus == Utils.BiometricStatus.OK) {
mSwitchBar.setChecked(false);
Utils.launchBiometricPromptForMandatoryBiometrics(this,
- REQUEST_BIOMETRIC_PROMPT, userId, false /* hideBackground */);
+ REQUEST_BIOMETRIC_PROMPT,
+ userId, false /* hideBackground */);
+ } else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
+ mSwitchBar.setChecked(false);
} else {
//Reset biometrics once enable dialog is shown
mIsBiometricsAuthenticated = false;
diff --git a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java
index cf6b3e33e76..a9f94b49b45 100644
--- a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java
@@ -225,13 +225,15 @@ public class BuildNumberPreferenceController extends BasePreferenceController im
if (requestCode == REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF
&& resultCode == Activity.RESULT_OK) {
final int userId = mContext.getUserId();
- if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
- false /* biometricsSuccessfullyAuthenticated */,
- false /* biometricsAuthenticationRequested */,
- userId)) {
+ final Utils.BiometricStatus biometricAuthStatus =
+ Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
+ false /* biometricsAuthenticationRequested */,
+ userId);
+ if (biometricAuthStatus == Utils.BiometricStatus.OK) {
Utils.launchBiometricPromptForMandatoryBiometrics(mFragment,
- REQUEST_IDENTITY_CHECK_FOR_DEV_PREF, userId, false /* hideBackground */);
- } else {
+ REQUEST_IDENTITY_CHECK_FOR_DEV_PREF,
+ userId, false /* hideBackground */);
+ } else if (biometricAuthStatus == Utils.BiometricStatus.NOT_ACTIVE) {
enableDevelopmentSettings();
}
} else if (requestCode == REQUEST_IDENTITY_CHECK_FOR_DEV_PREF
diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java
index b284c8d7c40..2294b9b5824 100644
--- a/src/com/android/settings/localepicker/AppLocalePickerActivity.java
+++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java
@@ -37,6 +37,7 @@ import android.view.View;
import android.widget.FrameLayout;
import android.widget.ListView;
+import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.view.ViewCompat;
@@ -67,6 +68,7 @@ public class AppLocalePickerActivity extends SettingsBaseActivity
private View mAppLocaleDetailContainer;
private NotificationController mNotificationController;
private MetricsFeatureProvider mMetricsFeatureProvider;
+ @Nullable private String mParentLocale;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -129,6 +131,11 @@ public class AppLocalePickerActivity extends SettingsBaseActivity
finish();
}
+ @Override
+ public void onParentLocaleSelected(LocaleStore.LocaleInfo localeInfo) {
+ mParentLocale = localeInfo.getFullNameNative();
+ }
+
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/);
@@ -258,6 +265,12 @@ public class AppLocalePickerActivity extends SettingsBaseActivity
super.onFragmentViewCreated(fm, f, v, s);
ListView listView = (ListView) v.findViewById(android.R.id.list);
if (listView != null) {
+ if (mParentLocale != null) {
+ mAppLocaleDetails = AppLocaleDetails.newInstance(mPackageName,
+ getUserId());
+ mAppLocaleDetailContainer = launchAppLocaleDetailsPage();
+ mAppLocaleDetails.setParentLocale(mParentLocale);
+ }
listView.addHeaderView(mAppLocaleDetailContainer);
}
}
diff --git a/src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java b/src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java
index 6e596e16883..05cb6a48d1e 100644
--- a/src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java
+++ b/src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java
@@ -39,6 +39,7 @@ public class LocalePickerWithRegionActivity extends SettingsBaseActivity
implements LocalePickerWithRegion.LocaleSelectedListener, MenuItem.OnActionExpandListener {
private static final String TAG = LocalePickerWithRegionActivity.class.getSimpleName();
private static final String PARENT_FRAGMENT_NAME = "localeListEditor";
+ private static final String CHILD_FRAGMENT_NAME = "LocalePickerWithRegion";
private LocalePickerWithRegion mSelector;
@@ -68,12 +69,15 @@ public class LocalePickerWithRegionActivity extends SettingsBaseActivity
explicitLocales,
null /* appPackageName */,
this);
- getFragmentManager()
- .beginTransaction()
- .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
- .replace(R.id.content_frame, mSelector)
- .addToBackStack(PARENT_FRAGMENT_NAME)
- .commit();
+
+ if (getFragmentManager().findFragmentByTag(CHILD_FRAGMENT_NAME) == null) {
+ getFragmentManager()
+ .beginTransaction()
+ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
+ .replace(R.id.content_frame, mSelector, CHILD_FRAGMENT_NAME)
+ .addToBackStack(PARENT_FRAGMENT_NAME)
+ .commit();
+ }
}
@Override
diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java
index 0fcfcb515a1..e2406826320 100644
--- a/src/com/android/settings/network/NetworkProviderSettings.java
+++ b/src/com/android/settings/network/NetworkProviderSettings.java
@@ -1068,6 +1068,10 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment
@VisibleForTesting
void launchNetworkDetailsFragment(LongPressWifiEntryPreference pref) {
final WifiEntry wifiEntry = pref.getWifiEntry();
+ if (!wifiEntry.isSaved()) {
+ Log.w(TAG, "launchNetworkDetailsFragment: Don't launch because WifiEntry isn't saved!");
+ return;
+ }
final Context context = requireContext();
final Bundle bundle = new Bundle();
diff --git a/src/com/android/settings/network/telephony/NetworkSelectRepository.kt b/src/com/android/settings/network/telephony/NetworkSelectRepository.kt
index 1f5fbc202f5..d95c90e5c19 100644
--- a/src/com/android/settings/network/telephony/NetworkSelectRepository.kt
+++ b/src/com/android/settings/network/telephony/NetworkSelectRepository.kt
@@ -18,8 +18,10 @@ package com.android.settings.network.telephony
import android.content.Context
import android.telephony.AccessNetworkConstants
+import android.telephony.CarrierConfigManager
import android.telephony.NetworkRegistrationInfo
import android.telephony.TelephonyManager
+import android.telephony.satellite.SatelliteManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
@@ -28,9 +30,11 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-class NetworkSelectRepository(context: Context, subId: Int) {
+class NetworkSelectRepository(context: Context, private val subId: Int) {
private val telephonyManager =
context.getSystemService(TelephonyManager::class.java)!!.createForSubscriptionId(subId)
+ private val satelliteManager = context.getSystemService(SatelliteManager::class.java)
+ private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)
data class NetworkRegistrationAndForbiddenInfo(
val networkList: List,
@@ -55,10 +59,21 @@ class NetworkSelectRepository(context: Context, subId: Int) {
if (telephonyManager.dataState != TelephonyManager.DATA_CONNECTED) return null
// Try to get the network registration states
val serviceState = telephonyManager.serviceState ?: return null
- val networkList = serviceState.getNetworkRegistrationInfoListForTransportType(
+ var networkList = serviceState.getNetworkRegistrationInfoListForTransportType(
AccessNetworkConstants.TRANSPORT_TYPE_WWAN
)
if (networkList.isEmpty()) return null
+
+ val satellitePlmn = getSatellitePlmns()
+ // If connected network is Satellite, filter out
+ if (satellitePlmn.isNotEmpty()) {
+ val filteredNetworkList = networkList.filter {
+ val cellIdentity = it.cellIdentity
+ val plmn = cellIdentity?.plmn
+ plmn != null && !satellitePlmn.contains(plmn)
+ }
+ networkList = filteredNetworkList
+ }
// Due to the aggregation of cell between carriers, it's possible to get CellIdentity
// containing forbidden PLMN.
// Getting current network from ServiceState is no longer a good idea.
@@ -72,4 +87,24 @@ class NetworkSelectRepository(context: Context, subId: Int) {
private fun getForbiddenPlmns(): List {
return telephonyManager.forbiddenPlmns?.toList() ?: emptyList()
}
+
+ /**
+ * Update satellite PLMNs from the satellite framework.
+ */
+ private fun getSatellitePlmns(): List {
+ val config = carrierConfigManager.getConfigForSubId(
+ subId,
+ CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL
+ )
+
+ val shouldFilter = config.getBoolean(
+ CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL,
+ true)
+
+ return if (shouldFilter) {
+ satelliteManager.getSatellitePlmnsForCarrier(subId)
+ } else {
+ emptyList();
+ }
+ }
}
diff --git a/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java
index 200a47b54eb..778c788e4c8 100644
--- a/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java
+++ b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java
@@ -226,7 +226,10 @@ public class AppChannelsBypassingDndPreferenceController extends NotificationPre
.setArguments(channelArgs)
.setUserHandle(UserHandle.of(mAppRow.userId))
.setTitleRes(com.android.settings.R.string.notification_channel_title)
- .setSourceMetricsCategory(SettingsEnums.DND_APPS_BYPASSING)
+ .setSourceMetricsCategory(
+ android.app.Flags.modesUi()
+ ? SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP_CHANNELS
+ : SettingsEnums.DND_APPS_BYPASSING)
.launch();
return true;
});
diff --git a/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java b/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java
index 4fab7e277ea..b5e2b130b27 100644
--- a/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java
+++ b/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java
@@ -40,7 +40,9 @@ public class AppChannelsBypassingDndSettings extends NotificationSettings {
@Override
public int getMetricsCategory() {
- return SettingsEnums.DND_APPS_BYPASSING;
+ return android.app.Flags.modesUi()
+ ? SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP_CHANNELS
+ : SettingsEnums.DND_APPS_BYPASSING;
}
@Override
diff --git a/src/com/android/settings/notification/modes/CircularIconsPreference.java b/src/com/android/settings/notification/modes/CircularIconsPreference.java
index 0766ccd5623..ccf7f529e46 100644
--- a/src/com/android/settings/notification/modes/CircularIconsPreference.java
+++ b/src/com/android/settings/notification/modes/CircularIconsPreference.java
@@ -49,7 +49,9 @@ public class CircularIconsPreference extends RestrictedPreference {
private static final float DISABLED_ITEM_ALPHA = 0.3f;
- record LoadedIcons(ImmutableList icons, int extraItems) { }
+ record LoadedIcons(ImmutableList icons, int extraItems) {
+ static final LoadedIcons EMPTY = new LoadedIcons(ImmutableList.of(), 0);
+ }
private Executor mUiExecutor;
@@ -126,6 +128,7 @@ public class CircularIconsPreference extends RestrictedPreference {
// We know what icons we want, but haven't yet loaded them.
if (mIconSet.size() == 0) {
container.setVisibility(View.GONE);
+ mLoadedIcons = LoadedIcons.EMPTY;
return;
}
container.setVisibility(View.VISIBLE);
@@ -137,7 +140,7 @@ public class CircularIconsPreference extends RestrictedPreference {
@Override
public void onGlobalLayout() {
container.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- startLoadingIcons(container, mIconSet);
+ notifyChanged();
}
}
);
diff --git a/src/com/android/settings/notification/modes/SetupInterstitialActivity.java b/src/com/android/settings/notification/modes/SetupInterstitialActivity.java
index f26de76844b..830baaf7bfa 100644
--- a/src/com/android/settings/notification/modes/SetupInterstitialActivity.java
+++ b/src/com/android/settings/notification/modes/SetupInterstitialActivity.java
@@ -25,6 +25,7 @@ import static android.app.AutomaticZenRule.TYPE_THEATER;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.app.ActionBar;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -164,7 +165,8 @@ public class SetupInterstitialActivity extends FragmentActivity {
// they happen to go back. Forward the activity result in case we got here (indirectly)
// from some app that is waiting for the result.
if (updated) {
- ZenSubSettingLauncher.forMode(this, modeId)
+ ZenSubSettingLauncher.forModeFragment(this, ZenModeFragment.class, modeId,
+ SettingsEnums.ZEN_MODE_INTERSTITIAL)
.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT).launch();
}
finish();
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsFragment.java b/src/com/android/settings/notification/modes/ZenModeAppsFragment.java
index 19035ddcf78..ec72c83f3a5 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsFragment.java
@@ -47,7 +47,6 @@ public class ZenModeAppsFragment extends ZenModeFragmentBase {
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - make this the correct metrics category
- return SettingsEnums.NOTIFICATION_ZEN_MODE_PRIORITY;
+ return SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APPS;
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
index 1521a8b4f09..45287abdc61 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
@@ -20,7 +20,10 @@ import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.app.Application;
+import android.app.settings.SettingsEnums;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
@@ -48,6 +51,7 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
+import java.util.function.Function;
/**
* Preference with a link and summary about what apps can break through the mode
@@ -64,24 +68,26 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
private ZenMode mZenMode;
private CircularIconsPreference mPreference;
private final Fragment mHost;
+ private final Function mAppIconRetriever;
ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host,
ZenModesBackend backend, ZenHelperBackend helperBackend) {
this(context, key, host,
ApplicationsState.getInstance((Application) context.getApplicationContext()),
- backend, helperBackend);
+ backend, helperBackend, appInfo -> Utils.getBadgedIcon(context, appInfo));
}
@VisibleForTesting
ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host,
ApplicationsState applicationsState, ZenModesBackend backend,
- ZenHelperBackend helperBackend) {
+ ZenHelperBackend helperBackend, Function appIconRetriever) {
super(context, key, backend);
mSummaryHelper = new ZenModeSummaryHelper(mContext, helperBackend);
mHelperBackend = helperBackend;
mApplicationsState = applicationsState;
mUserManager = context.getSystemService(UserManager.class);
mHost = host;
+ mAppIconRetriever = appIconRetriever;
}
@Override
@@ -93,10 +99,9 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
- // TODO(b/332937635): Update metrics category
preference.setIntent(
ZenSubSettingLauncher.forModeFragment(mContext, ZenModeAppsFragment.class,
- zenMode.getId(), 0).toIntent());
+ zenMode.getId(), SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
preference.setEnabled(zenMode.isEnabled());
mZenMode = zenMode;
@@ -105,13 +110,18 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) {
mPreference.setSummary(R.string.zen_mode_apps_none_apps);
mPreference.displayIcons(CircularIconSet.EMPTY);
+ if (mAppSession != null) {
+ mAppSession.deactivateSession();
+ }
} else {
if (TextUtils.isEmpty(mPreference.getSummary())) {
mPreference.setSummary(R.string.zen_mode_apps_calculating);
}
- if (mApplicationsState != null && mHost != null) {
+ if (mAppSession == null) {
mAppSession = mApplicationsState.newSession(mAppSessionCallbacks,
mHost.getLifecycle());
+ } else {
+ mAppSession.activateSession();
}
triggerUpdateAppsBypassingDnd();
}
@@ -133,12 +143,16 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
}
private void displayAppsBypassingDnd(List allApps) {
+ if (mZenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) {
+ // Can get this callback when resuming, if we had CHANNEL_POLICY_PRIORITY and just
+ // switched to CHANNEL_POLICY_NONE.
+ return;
+ }
+
ImmutableList apps = getAppsBypassingDndSortedByName(allApps);
-
mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, apps));
-
mPreference.displayIcons(new CircularIconSet<>(apps,
- app -> Utils.getBadgedIcon(mContext, app.info)),
+ app -> mAppIconRetriever.apply(app.info)),
APP_ENTRY_EQUIVALENCE);
}
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
index 522f191c37f..c44661a9d09 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
@@ -106,10 +106,9 @@ public class ZenModeAppsPreferenceController extends
if (mModeId != null) {
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, mModeId);
}
- // TODO(b/332937635): Update metrics category
new SubSettingLauncher(mContext)
.setDestination(ZenModeSelectBypassingAppsFragment.class.getName())
- .setSourceMetricsCategory(SettingsEnums.SETTINGS_ZEN_NOTIFICATIONS)
+ .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APPS)
.setArguments(bundle)
.launch();
}
diff --git a/src/com/android/settings/notification/modes/ZenModeCallsFragment.java b/src/com/android/settings/notification/modes/ZenModeCallsFragment.java
index 54072ac185a..ac05328d2d3 100644
--- a/src/com/android/settings/notification/modes/ZenModeCallsFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeCallsFragment.java
@@ -50,7 +50,6 @@ public class ZenModeCallsFragment extends ZenModeFragmentBase {
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.DND_CALLS;
}
diff --git a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
index d8850191762..efddcf9e23a 100644
--- a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
@@ -18,6 +18,7 @@ package com.android.settings.notification.modes;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
@@ -41,10 +42,9 @@ class ZenModeCallsLinkPreferenceController extends AbstractZenModePreferenceCont
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
- // TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeCallsFragment.class.getName())
- .setSourceMetricsCategory(0)
+ .setSourceMetricsCategory(SettingsEnums.DND_PEOPLE)
.setArguments(bundle)
.toIntent());
preference.setSummary(mSummaryHelper.getCallsSettingSummary(zenMode));
diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayFragment.java b/src/com/android/settings/notification/modes/ZenModeDisplayFragment.java
index 38ac8f31072..74ed38f96bd 100644
--- a/src/com/android/settings/notification/modes/ZenModeDisplayFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeDisplayFragment.java
@@ -54,7 +54,6 @@ public class ZenModeDisplayFragment extends ZenModeFragmentBase {
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - make this the correct metrics category
- return SettingsEnums.DND_PEOPLE;
+ return SettingsEnums.ZEN_MODE_DISPLAY_SETTINGS;
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
index bba5e342732..57dce89a35c 100644
--- a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
@@ -18,6 +18,7 @@ package com.android.settings.notification.modes;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
@@ -41,10 +42,9 @@ class ZenModeDisplayLinkPreferenceController extends AbstractZenModePreferenceCo
void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
- // TODO(b/332937635): Update metrics category
preference.setIntent(
ZenSubSettingLauncher.forModeFragment(mContext, ZenModeDisplayFragment.class,
- zenMode.getId(), 0).toIntent());
+ zenMode.getId(), SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
preference.setEnabled(zenMode.isEnabled());
}
diff --git a/src/com/android/settings/notification/modes/ZenModeEditNameIconFragment.java b/src/com/android/settings/notification/modes/ZenModeEditNameIconFragment.java
index a0c2cf1109b..60f731627f8 100644
--- a/src/com/android/settings/notification/modes/ZenModeEditNameIconFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeEditNameIconFragment.java
@@ -72,8 +72,7 @@ public class ZenModeEditNameIconFragment extends ZenModeEditNameIconFragmentBase
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - make this the correct metrics category
- return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
+ return SettingsEnums.ZEN_MODE_EDIT_NAME_ICON;
}
@Override
diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java
index 0a80977a021..37772990152 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragment.java
@@ -129,8 +129,7 @@ public class ZenModeFragment extends ZenModeFragmentBase {
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - make this the correct metrics category
- return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
+ return SettingsEnums.ZEN_PRIORITY_MODE;
}
@Override
@@ -164,9 +163,8 @@ public class ZenModeFragment extends ZenModeFragmentBase {
@Override
public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
if (menuItem.getItemId() == RENAME_MODE) {
- // TODO: b/332937635 - Update metrics category
ZenSubSettingLauncher.forModeFragment(mContext, ZenModeEditNameIconFragment.class,
- mZenMode.getId(), 0).launch();
+ mZenMode.getId(), getMetricsCategory()).launch();
} else if (menuItem.getItemId() == DELETE_MODE) {
new AlertDialog.Builder(mContext)
.setTitle(mContext.getString(R.string.zen_mode_delete_mode_confirmation,
diff --git a/src/com/android/settings/notification/modes/ZenModeMessagesFragment.java b/src/com/android/settings/notification/modes/ZenModeMessagesFragment.java
index 8bf574f4ced..709e5da03df 100644
--- a/src/com/android/settings/notification/modes/ZenModeMessagesFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeMessagesFragment.java
@@ -46,7 +46,6 @@ public class ZenModeMessagesFragment extends ZenModeFragmentBase {
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.DND_MESSAGES;
}
diff --git a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
index 4c0b758e7cb..50d7958120b 100644
--- a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
@@ -18,6 +18,7 @@ package com.android.settings.notification.modes;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
@@ -40,10 +41,9 @@ class ZenModeMessagesLinkPreferenceController extends AbstractZenModePreferenceC
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
- // TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeMessagesFragment.class.getName())
- .setSourceMetricsCategory(0)
+ .setSourceMetricsCategory(SettingsEnums.DND_PEOPLE)
.setArguments(bundle)
.toIntent());
diff --git a/src/com/android/settings/notification/modes/ZenModeNewCustomFragment.java b/src/com/android/settings/notification/modes/ZenModeNewCustomFragment.java
index 6086c0c7b38..d7dbaaf8702 100644
--- a/src/com/android/settings/notification/modes/ZenModeNewCustomFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeNewCustomFragment.java
@@ -16,6 +16,8 @@
package com.android.settings.notification.modes;
+import android.app.settings.SettingsEnums;
+
import androidx.annotation.Nullable;
import com.android.settings.R;
@@ -50,15 +52,15 @@ public class ZenModeNewCustomFragment extends ZenModeEditNameIconFragmentBase {
if (created != null) {
// Open the mode view fragment and close the "add mode" fragment, so exiting the mode
// view goes back to previous screen (which should be the modes list).
- ZenSubSettingLauncher.forMode(requireContext(), created.getId()).launch();
+ ZenSubSettingLauncher.forModeFragment(requireContext(), ZenModeFragment.class,
+ created.getId(), getMetricsCategory()).launch();
finish();
}
}
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - make this the correct metrics category
- return 0;
+ return SettingsEnums.ZEN_MODE_ADD_NEW;
}
@Override
diff --git a/src/com/android/settings/notification/modes/ZenModeNotifVisFragment.java b/src/com/android/settings/notification/modes/ZenModeNotifVisFragment.java
index 3fdfec6e106..d1bd493482a 100644
--- a/src/com/android/settings/notification/modes/ZenModeNotifVisFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeNotifVisFragment.java
@@ -19,6 +19,7 @@ package com.android.settings.notification.modes;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.service.notification.ZenPolicy;
+
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -57,7 +58,6 @@ public class ZenModeNotifVisFragment extends ZenModeFragmentBase {
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - make this the correct metrics category
- return SettingsEnums.DND_PEOPLE;
+ return SettingsEnums.ZEN_CUSTOM_RULE_VIS_EFFECTS;
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
index 622c4a2db48..cd1e8c7d3ba 100644
--- a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
@@ -19,6 +19,7 @@ package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
@@ -47,10 +48,9 @@ class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePreferenceC
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
- // TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeNotifVisFragment.class.getName())
- .setSourceMetricsCategory(0)
+ .setSourceMetricsCategory(SettingsEnums.ZEN_MODE_DISPLAY_SETTINGS)
.setArguments(bundle)
.toIntent());
}
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherFragment.java b/src/com/android/settings/notification/modes/ZenModeOtherFragment.java
index 1149cd1312f..28b2e54d3a6 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherFragment.java
@@ -16,14 +16,9 @@
package com.android.settings.notification.modes;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_EVENTS;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
-
import android.app.settings.SettingsEnums;
import android.content.Context;
+
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -58,7 +53,6 @@ public class ZenModeOtherFragment extends ZenModeFragmentBase {
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.NOTIFICATION_ZEN_MODE_PRIORITY;
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
index 15e0edcf1df..5b26364c938 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
@@ -23,6 +23,7 @@ import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.service.notification.ZenPolicy;
@@ -65,10 +66,9 @@ class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceCont
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
- // TODO: b/332937635 - Update metrics category
preference.setIntent(
ZenSubSettingLauncher.forModeFragment(mContext, ZenModeOtherFragment.class,
- zenMode.getId(), 0).toIntent());
+ zenMode.getId(), SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
preference.setEnabled(zenMode.isEnabled());
preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode));
diff --git a/src/com/android/settings/notification/modes/ZenModePeopleFragment.java b/src/com/android/settings/notification/modes/ZenModePeopleFragment.java
index f541d132010..11e4453ddef 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleFragment.java
@@ -48,7 +48,6 @@ public class ZenModePeopleFragment extends ZenModeFragmentBase {
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.DND_PEOPLE;
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
index 4610c35ca82..9aad4603588 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
@@ -26,6 +26,7 @@ import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.graphics.drawable.Drawable;
@@ -88,10 +89,10 @@ class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceCon
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
- // TODO(b/332937635): Update metrics category
+ // Passes in source ZenModeFragment metric category.
preference.setIntent(
ZenSubSettingLauncher.forModeFragment(mContext, ZenModePeopleFragment.class,
- zenMode.getId(), 0).toIntent());
+ zenMode.getId(), SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
preference.setEnabled(zenMode.isEnabled());
preference.setSummary(mSummaryHelper.getPeopleSummary(zenMode.getPolicy()));
diff --git a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
index ab5e2d9e56b..11b65bd21f5 100644
--- a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
@@ -270,10 +270,9 @@ class ZenModePrioritySendersPreferenceController
mContext.startActivity(ALL_CONTACTS_INTENT);
} else if (KEY_ANY_CONVERSATIONS.equals(key)
|| KEY_IMPORTANT_CONVERSATIONS.equals(key)) {
- // TODO: b/332937635 - set correct metrics category
new SubSettingLauncher(mContext)
.setDestination(ConversationListSettings.class.getName())
- .setSourceMetricsCategory(SettingsEnums.DND_CONVERSATIONS)
+ .setSourceMetricsCategory(SettingsEnums.DND_MESSAGES)
.launch();
} else {
mContext.startActivity(FALLBACK_INTENT);
diff --git a/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java b/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java
index 6202648fcf9..d129aad6ad6 100644
--- a/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java
+++ b/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java
@@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import android.app.Dialog;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
@@ -70,8 +71,7 @@ public class ZenModeScheduleChooserDialog extends InstrumentedDialogFragment {
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - Update metrics category
- return 0;
+ return SettingsEnums.ZEN_SCHEDULE_CHOOSER_DIALOG;
}
static void show(DashboardFragment parent, OnScheduleOptionListener optionListener) {
diff --git a/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java b/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java
index 8b682b92f1a..1f5438d08eb 100644
--- a/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java
@@ -74,8 +74,7 @@ public class ZenModeSelectBypassingAppsFragment extends ZenModeFragmentBase impl
@Override
public int getMetricsCategory() {
- // TODO(b/332937635): Update metrics category
- return SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APPS;
+ return SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP;
}
/**
diff --git a/src/com/android/settings/notification/modes/ZenModeSetCalendarFragment.java b/src/com/android/settings/notification/modes/ZenModeSetCalendarFragment.java
index f0206ef5dad..a266c8b1637 100644
--- a/src/com/android/settings/notification/modes/ZenModeSetCalendarFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeSetCalendarFragment.java
@@ -46,7 +46,6 @@ public class ZenModeSetCalendarFragment extends ZenModeFragmentBase {
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.NOTIFICATION_ZEN_MODE_EVENT_RULE;
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeSetScheduleFragment.java b/src/com/android/settings/notification/modes/ZenModeSetScheduleFragment.java
index 4d58097b1dc..91197845874 100644
--- a/src/com/android/settings/notification/modes/ZenModeSetScheduleFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeSetScheduleFragment.java
@@ -48,7 +48,6 @@ public class ZenModeSetScheduleFragment extends ZenModeFragmentBase {
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.NOTIFICATION_ZEN_MODE_SCHEDULE_RULE;
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeTimePickerFragment.java b/src/com/android/settings/notification/modes/ZenModeTimePickerFragment.java
index d8e1b38875b..3fa53946477 100644
--- a/src/com/android/settings/notification/modes/ZenModeTimePickerFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeTimePickerFragment.java
@@ -62,7 +62,6 @@ public class ZenModeTimePickerFragment extends InstrumentedDialogFragment implem
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - set correct metrics category (or decide to keep this one?)
return SettingsEnums.DIALOG_ZEN_TIMEPICKER;
}
diff --git a/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java b/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java
index 885c4db1fa1..1add4889c66 100644
--- a/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java
@@ -24,6 +24,7 @@ import static android.service.notification.ZenModeConfig.tryParseScheduleConditi
import android.annotation.SuppressLint;
import android.app.AlertDialog;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -97,9 +98,9 @@ class ZenModeTriggerUpdatePreferenceController extends AbstractZenModePreference
private void setUpForSystemOwnedTrigger(Preference preference, ZenMode mode) {
if (mode.getType() == TYPE_SCHEDULE_TIME) {
- // TODO: b/332937635 - set correct metrics category
preference.setIntent(ZenSubSettingLauncher.forModeFragment(mContext,
- ZenModeSetScheduleFragment.class, mode.getId(), 0).toIntent());
+ ZenModeSetScheduleFragment.class, mode.getId(),
+ SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
// [Clock Icon] 9:00 - 17:00 / Sun-Mon
preference.setIcon(com.android.internal.R.drawable.ic_zen_mode_type_schedule_time);
@@ -115,9 +116,9 @@ class ZenModeTriggerUpdatePreferenceController extends AbstractZenModePreference
preference.setSummary(null);
}
} else if (mode.getType() == TYPE_SCHEDULE_CALENDAR) {
- // TODO: b/332937635 - set correct metrics category
preference.setIntent(ZenSubSettingLauncher.forModeFragment(mContext,
- ZenModeSetCalendarFragment.class, mode.getId(), 0).toIntent());
+ ZenModeSetCalendarFragment.class, mode.getId(),
+ SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
// [Event Icon] Calendar Events /
preference.setIcon(
diff --git a/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java b/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java
index 57d3bf96c2b..e7905a8f936 100644
--- a/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java
+++ b/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java
@@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import android.app.Dialog;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -56,8 +57,7 @@ public class ZenModesListAddModeTypeChooserDialog extends InstrumentedDialogFrag
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - Update metrics category
- return 0;
+ return SettingsEnums.ZEN_MODE_NEW_TYPE_CHOOSER_DIALOG;
}
static void show(DashboardFragment parent,
diff --git a/src/com/android/settings/notification/modes/ZenModesListFragment.java b/src/com/android/settings/notification/modes/ZenModesListFragment.java
index cab0209e06c..2b58f8e1c3b 100644
--- a/src/com/android/settings/notification/modes/ZenModesListFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModesListFragment.java
@@ -74,8 +74,7 @@ public class ZenModesListFragment extends ZenModesFragmentBase {
@Override
public int getMetricsCategory() {
- // TODO: b/332937635 - add new & set metrics categories correctly
- return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
+ return SettingsEnums.ZEN_PRIORITY_MODES_LIST;
}
private void onAvailableModeTypesForAdd(List types) {
@@ -97,10 +96,9 @@ public class ZenModesListFragment extends ZenModesFragmentBase {
startActivityForResult(type.creationActivityIntent(), REQUEST_NEW_MODE);
} else {
// Custom-manual mode -> "add a mode" screen.
- // TODO: b/332937635 - set metrics categories correctly
new SubSettingLauncher(requireContext())
.setDestination(ZenModeNewCustomFragment.class.getName())
- .setSourceMetricsCategory(0)
+ .setSourceMetricsCategory(SettingsEnums.ZEN_PRIORITY_MODES_LIST)
.launch();
}
}
@@ -125,7 +123,9 @@ public class ZenModesListFragment extends ZenModesFragmentBase {
.filter(m -> m.getRule().getPackageName().equals(activityInvoked.getPackageName()))
.findFirst();
createdZenMode.ifPresent(
- mode -> ZenSubSettingLauncher.forMode(mContext, mode.getId()).launch());
+ mode ->
+ ZenSubSettingLauncher.forModeFragment(mContext, ZenModeFragment.class,
+ mode.getId(), getMetricsCategory()).launch());
}
/**
diff --git a/src/com/android/settings/notification/modes/ZenModesListItemPreference.java b/src/com/android/settings/notification/modes/ZenModesListItemPreference.java
index e09d04c7737..0c961481193 100644
--- a/src/com/android/settings/notification/modes/ZenModesListItemPreference.java
+++ b/src/com/android/settings/notification/modes/ZenModesListItemPreference.java
@@ -15,6 +15,7 @@
*/
package com.android.settings.notification.modes;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.widget.TextView;
@@ -63,7 +64,8 @@ class ZenModesListItemPreference extends RestrictedPreference {
@Override
public void onClick() {
- ZenSubSettingLauncher.forMode(mContext, mZenMode.getId()).launch();
+ ZenSubSettingLauncher.forModeFragment(mContext, ZenModeFragment.class, mZenMode.getId(),
+ SettingsEnums.ZEN_PRIORITY_MODES_LIST).launch();
}
public void setZenMode(ZenMode zenMode) {
diff --git a/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java b/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java
index 00c21bbe4cd..c02a9d95f20 100644
--- a/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java
+++ b/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java
@@ -18,7 +18,6 @@ package com.android.settings.notification.modes;
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
-import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
@@ -26,12 +25,6 @@ import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
class ZenSubSettingLauncher {
-
- static SubSettingLauncher forMode(Context context, String modeId) {
- return forModeFragment(context, ZenModeFragment.class, modeId,
- SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION);
- }
-
static SubSettingLauncher forModeFragment(Context context,
Class extends DashboardFragment> fragmentClass, String modeId,
int sourceMetricsCategory) {
diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java
index 34c0731184e..09091102f24 100644
--- a/src/com/android/settings/password/ChooseLockGeneric.java
+++ b/src/com/android/settings/password/ChooseLockGeneric.java
@@ -491,11 +491,16 @@ public class ChooseLockGeneric extends SettingsActivity {
? data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD)
: null;
updatePreferencesOrFinish(false /* isRecreatingActivity */);
- if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getContext(),
- mBiometricsAuthSuccessful, mWaitingForConfirmation, mUserId)) {
- mWaitingForConfirmation = true;
- Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST,
+ final Utils.BiometricStatus biometricAuthStatus =
+ Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
+ false /* biometricsAuthenticationRequested */,
+ mUserId);
+ if (biometricAuthStatus == Utils.BiometricStatus.OK) {
+ Utils.launchBiometricPromptForMandatoryBiometrics(this,
+ BIOMETRIC_AUTH_REQUEST,
mUserId, true /* hideBackground */);
+ } else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
+ finish();
}
} else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
if (resultCode == Activity.RESULT_OK) {
diff --git a/src/com/android/settings/slices/SlicePreferenceController.java b/src/com/android/settings/slices/SlicePreferenceController.java
index 5e8fb26eeb2..2e835a03cde 100644
--- a/src/com/android/settings/slices/SlicePreferenceController.java
+++ b/src/com/android/settings/slices/SlicePreferenceController.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.net.Uri;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
@@ -61,7 +62,8 @@ public class SlicePreferenceController extends BasePreferenceController implemen
return mUri != null ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
- public void setSliceUri(Uri uri) {
+ /** Sets Slice uri for the preference. */
+ public void setSliceUri(@Nullable Uri uri) {
mUri = uri;
mLiveData = SliceLiveData.fromUri(mContext, mUri, (int type, Throwable source) -> {
Log.w(TAG, "Slice may be null. uri = " + uri + ", error = " + type);
diff --git a/src/com/android/settings/users/UserCapabilities.java b/src/com/android/settings/users/UserCapabilities.java
index 590cb0cf11a..60e92a8c2ce 100644
--- a/src/com/android/settings/users/UserCapabilities.java
+++ b/src/com/android/settings/users/UserCapabilities.java
@@ -76,6 +76,9 @@ public class UserCapabilities {
public void updateAddUserCapabilities(Context context) {
final UserManager userManager =
(UserManager) context.getSystemService(Context.USER_SERVICE);
+ final UserInfo myUserInfo = userManager.getUserInfo(UserHandle.myUserId());
+ mIsAdmin = myUserInfo.isAdmin();
+
mEnforcedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
UserManager.DISALLOW_ADD_USER, UserHandle.myUserId());
final boolean hasBaseUserRestriction = RestrictedLockUtilsInternal.hasBaseUserRestriction(
diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java
index 66c278ed733..8afab9678f4 100644
--- a/src/com/android/settings/users/UserDetailsSettings.java
+++ b/src/com/android/settings/users/UserDetailsSettings.java
@@ -570,7 +570,9 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
* OR multiple admin support is NOT enabled.
* OR the current user has DISALLOW_GRANT_ADMIN restriction applied
*
- * OR the target user ('mUserInfo') is a main user OR a guest user.
+ * OR the target user ('mUserInfo') is a main user
+ * OR the target user ('mUserInfo') is not of type
+ * {@link UserManager#USER_TYPE_FULL_SECONDARY}
* OR the target user ('mUserInfo') has DISALLOW_GRANT_ADMIN restriction.
*
*
@@ -582,7 +584,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
|| mUserManager.hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN);
boolean targetUserRestricted = mUserInfo.isMain()
- || mUserInfo.isGuest()
+ || !(UserManager.USER_TYPE_FULL_SECONDARY.equals(mUserInfo.userType))
|| mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_GRANT_ADMIN,
mUserInfo.getUserHandle());
diff --git a/tests/robotests/src/com/android/settings/MainClearTest.java b/tests/robotests/src/com/android/settings/MainClearTest.java
index 26a430b161c..b705ae14cac 100644
--- a/tests/robotests/src/com/android/settings/MainClearTest.java
+++ b/tests/robotests/src/com/android/settings/MainClearTest.java
@@ -142,7 +142,7 @@ public class MainClearTest {
when(mMockActivity.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager);
when(mBiometricManager.canAuthenticate(anyInt(),
eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
- .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+ .thenReturn(BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE);
}
@After
@@ -388,6 +388,30 @@ public class MainClearTest {
verify(mMainClear, times(0)).showFinalConfirmation();
}
+ @Test
+ @EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testOnActivityResultInternal_keyguardRequestNotTriggeringBiometricPrompt_lockoutError() {
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mMockActivity.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager);
+ when(mResources.getString(anyInt())).thenReturn(TEST_ACCOUNT_NAME);
+ when(mBiometricManager.canAuthenticate(anyInt(),
+ eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
+ .thenReturn(BiometricManager.BIOMETRIC_ERROR_LOCKOUT);
+ doReturn(true).when(mMainClear).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST));
+ doNothing().when(mMainClear).startActivityForResult(any(), anyInt());
+ doReturn(mMockActivity).when(mMainClear).getActivity();
+ doReturn(mContext).when(mMainClear).getContext();
+
+ mMainClear
+ .onActivityResultInternal(MainClear.KEYGUARD_REQUEST, Activity.RESULT_OK, null);
+
+ verify(mMainClear).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST));
+ verify(mMainClear, never()).startActivityForResult(any(), eq(MainClear.BIOMETRICS_REQUEST));
+ verify(mMainClear, never()).establishInitialState();
+ verify(mMainClear, never()).getAccountConfirmationIntent();
+ verify(mMainClear, never()).showFinalConfirmation();
+ }
+
@Test
public void testOnActivityResultInternal_biometricRequestTriggeringFinalConfirmation() {
doReturn(true).when(mMainClear).isValidRequestCode(eq(MainClear.BIOMETRICS_REQUEST));
@@ -397,10 +421,10 @@ public class MainClearTest {
mMainClear
.onActivityResultInternal(MainClear.BIOMETRICS_REQUEST, Activity.RESULT_OK, null);
- verify(mMainClear, times(1)).isValidRequestCode(eq(MainClear.BIOMETRICS_REQUEST));
- verify(mMainClear, times(0)).establishInitialState();
- verify(mMainClear, times(1)).getAccountConfirmationIntent();
- verify(mMainClear, times(1)).showFinalConfirmation();
+ verify(mMainClear).isValidRequestCode(eq(MainClear.BIOMETRICS_REQUEST));
+ verify(mMainClear, never()).establishInitialState();
+ verify(mMainClear).getAccountConfirmationIntent();
+ verify(mMainClear).showFinalConfirmation();
}
@Test
diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java
index 2aeb9063b1e..107a1b333df 100644
--- a/tests/robotests/src/com/android/settings/UtilsTest.java
+++ b/tests/robotests/src/com/android/settings/UtilsTest.java
@@ -530,40 +530,40 @@ public class UtilsTest {
@Test
@EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
- public void testRequestBiometricAuthentication_biometricManagerNull_shouldReturnFalse() {
+ public void testRequestBiometricAuthentication_biometricManagerNull_shouldReturnNotActive() {
when(mContext.getSystemService(BiometricManager.class)).thenReturn(null);
assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
- false /* biometricsSuccessfullyAuthenticated */,
- false /* biometricsAuthenticationRequested */, USER_ID)).isFalse();
+ false /* biometricsAuthenticationRequested */, USER_ID)).isEqualTo(
+ Utils.BiometricStatus.NOT_ACTIVE);
}
@Test
@EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
- public void testRequestBiometricAuthentication_biometricManagerReturnsSuccess_shouldReturnTrue() {
+ public void testRequestBiometricAuthentication_biometricManagerReturnsSuccess_shouldReturnOk() {
when(mBiometricManager.canAuthenticate(USER_ID,
BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
.thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
- final boolean requestBiometricAuthenticationForMandatoryBiometrics =
+ final Utils.BiometricStatus requestBiometricAuthenticationForMandatoryBiometrics =
Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
- false /* biometricsSuccessfullyAuthenticated */,
- false /* biometricsAuthenticationRequested */, USER_ID);
- assertThat(requestBiometricAuthenticationForMandatoryBiometrics).isTrue();
+ false /* biometricsAuthenticationRequested */, USER_ID);
+ assertThat(requestBiometricAuthenticationForMandatoryBiometrics).isEqualTo(
+ Utils.BiometricStatus.OK);
}
@Test
@EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
- public void testRequestBiometricAuthentication_biometricManagerReturnsError_shouldReturnFalse() {
+ public void testRequestBiometricAuthentication_biometricManagerReturnsError_shouldReturnError() {
when(mBiometricManager.canAuthenticate(anyInt(),
eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
.thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
- false /* biometricsSuccessfullyAuthenticated */,
- false /* biometricsAuthenticationRequested */, USER_ID)).isFalse();
+ false /* biometricsAuthenticationRequested */, USER_ID)).isEqualTo(
+ Utils.BiometricStatus.ERROR);
}
@Test
@EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
- public void testRequestBiometricAuthentication_biometricManagerReturnsSuccessForDifferentUser_shouldReturnFalse() {
+ public void testRequestBiometricAuthentication_biometricManagerReturnsSuccessForDifferentUser_shouldReturnError() {
when(mBiometricManager.canAuthenticate(anyInt(),
eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
.thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
@@ -571,8 +571,8 @@ public class UtilsTest {
BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
.thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
- false /* biometricsSuccessfullyAuthenticated */,
- false /* biometricsAuthenticationRequested */, USER_ID)).isFalse();
+ false /* biometricsAuthenticationRequested */, USER_ID)).isEqualTo(
+ Utils.BiometricStatus.ERROR);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java
index 4f8860e8832..b4605c74785 100644
--- a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java
@@ -129,7 +129,7 @@ public class CombinedBiometricProfileSettingsTest {
doReturn(mBiometricManager).when(mActivity).getSystemService(BiometricManager.class);
when(mBiometricManager.canAuthenticate(anyInt(),
eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
- .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+ .thenReturn(BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE);
ReflectionHelpers.setField(mFragment, "mDashboardFeatureProvider",
FakeFeatureFactory.setupForTest().dashboardFeatureProvider);
@@ -187,6 +187,8 @@ public class CombinedBiometricProfileSettingsTest {
mFragment.onAttach(mContext);
mFragment.onCreate(null);
+ mFragment.onActivityResult(CONFIRM_REQUEST, RESULT_FINISHED,
+ new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L));
verify(mFragment).startActivityForResult(intentArgumentCaptor.capture(),
eq(BiometricsSettingsBase.BIOMETRIC_AUTH_REQUEST));
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
index ca76c1e6069..0e1bcf6348c 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
@@ -20,6 +20,8 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWE
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
import static com.android.settings.biometrics.BiometricEnrollBase.BIOMETRIC_AUTH_REQUEST;
+import static com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST;
+import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED;
import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment;
import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment.CHOOSE_LOCK_GENERIC_REQUEST;
import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment.KEY_REQUIRE_SCREEN_ON_TO_AUTH;
@@ -146,7 +148,7 @@ public class FingerprintSettingsFragmentTest {
doReturn(mBiometricManager).when(mContext).getSystemService(BiometricManager.class);
doReturn(true).when(mFingerprintManager).isHardwareDetected();
doReturn(mVibrator).when(mContext).getSystemService(Vibrator.class);
- when(mBiometricManager.canAuthenticate(
+ when(mBiometricManager.canAuthenticate(PRIMARY_USER_ID,
BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
.thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
}
@@ -170,20 +172,23 @@ public class FingerprintSettingsFragmentTest {
}
@Test
+ @Ignore("b/353706169")
@EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
public void testLaunchBiometricPromptForFingerprint() {
- when(mBiometricManager.canAuthenticate(
+ when(mBiometricManager.canAuthenticate(PRIMARY_USER_ID,
BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
.thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
-
+ doNothing().when(mFingerprintManager).generateChallenge(anyInt(), any());
+ when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true);
setUpFragment(false);
- ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(
- Intent.class);
+ ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+ mFragment.onActivityResult(CONFIRM_REQUEST, RESULT_FINISHED,
+ new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L));
verify(mFragment).startActivityForResult(intentArgumentCaptor.capture(),
eq(BIOMETRIC_AUTH_REQUEST));
- Intent intent = intentArgumentCaptor.getValue();
+ final Intent intent = intentArgumentCaptor.getValue();
assertThat(intent.getComponent().getClassName()).isEqualTo(
ConfirmDeviceCredentialActivity.InternalActivity.class.getName());
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
index 50aa7719ccb..19d0eddd3a4 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
@@ -50,6 +50,7 @@ import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
+import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -101,6 +102,8 @@ public class BluetoothDeviceDetailsFragmentTest {
private InputManager mInputManager;
@Mock
private CompanionDeviceManager mCompanionDeviceManager;
+ @Mock
+ private DeviceDetailsFragmentFormatter mFormatter;
@Before
public void setUp() {
@@ -111,7 +114,10 @@ public class BluetoothDeviceDetailsFragmentTest {
.getSystemService(CompanionDeviceManager.class);
when(mCompanionDeviceManager.getAllAssociations()).thenReturn(ImmutableList.of());
removeInputDeviceWithMatchingBluetoothAddress();
- FakeFeatureFactory.setupForTest();
+ FakeFeatureFactory fakeFeatureFactory = FakeFeatureFactory.setupForTest();
+ when(fakeFeatureFactory.mBluetoothFeatureProvider.getDeviceDetailsFragmentFormatter(any(),
+ any(), any(), eq(mCachedDevice))).thenReturn(mFormatter);
+ when(mFormatter.getVisiblePreferenceKeysForMainPage()).thenReturn(null);
mFragment = setupFragment();
mFragment.onAttach(mContext);
diff --git a/tests/robotests/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractorTest.kt b/tests/robotests/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractorTest.kt
new file mode 100644
index 00000000000..a83b7c2780e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractorTest.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2024 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.bluetooth.domain.interactor
+
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothProfile
+import android.content.Context
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import androidx.test.core.app.ApplicationProvider
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LeAudioProfile
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
+import com.android.settingslib.media.data.repository.SpatializerRepository
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.robolectric.RobolectricTestRunner
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(RobolectricTestRunner::class)
+class SpatialAudioInteractorTest {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var audioManager: AudioManager
+ @Mock private lateinit var cachedDevice: CachedBluetoothDevice
+ @Mock private lateinit var bluetoothDevice: BluetoothDevice
+ @Mock private lateinit var spatializerRepository: SpatializerRepository
+ @Mock private lateinit var leAudioProfile: LeAudioProfile
+
+ private lateinit var underTest: SpatialAudioInteractor
+ private val testScope = TestScope()
+
+ @Before
+ fun setUp() {
+ val context = spy(ApplicationProvider.getApplicationContext())
+ `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+ `when`(cachedDevice.address).thenReturn(BLUETOOTH_ADDRESS)
+ `when`(leAudioProfile.profileId).thenReturn(BluetoothProfile.LE_AUDIO)
+ underTest =
+ SpatialAudioInteractorImpl(
+ context,
+ audioManager,
+ SpatializerInteractor(spatializerRepository),
+ testScope.backgroundScope,
+ testScope.testScheduler)
+ }
+
+ @Test
+ fun getDeviceSetting_noAudioProfile_returnNull() {
+ testScope.runTest {
+ val setting = getLatestValue(underTest.getDeviceSetting(cachedDevice))
+
+ assertThat(setting).isNull()
+ verifyNoInteractions(spatializerRepository)
+ }
+ }
+
+ @Test
+ fun getDeviceSetting_audioProfileNotEnabled_returnNull() {
+ testScope.runTest {
+ `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
+ `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(false)
+
+ val setting = getLatestValue(underTest.getDeviceSetting(cachedDevice))
+
+ assertThat(setting).isNull()
+ verifyNoInteractions(spatializerRepository)
+ }
+ }
+
+ @Test
+ fun getDeviceSetting_spatialAudioNotSupported_returnNull() {
+ testScope.runTest {
+ `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
+ `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
+ `when`(
+ spatializerRepository.isSpatialAudioAvailableForDevice(
+ BLE_AUDIO_DEVICE_ATTRIBUTES))
+ .thenReturn(false)
+
+ val setting = getLatestValue(underTest.getDeviceSetting(cachedDevice))
+
+ assertThat(setting).isNull()
+ }
+ }
+
+ @Test
+ fun getDeviceSetting_spatialAudioSupported_returnTwoToggles() {
+ testScope.runTest {
+ `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
+ `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
+ `when`(
+ spatializerRepository.isSpatialAudioAvailableForDevice(
+ BLE_AUDIO_DEVICE_ATTRIBUTES))
+ .thenReturn(true)
+ `when`(
+ spatializerRepository.isHeadTrackingAvailableForDevice(
+ BLE_AUDIO_DEVICE_ATTRIBUTES))
+ .thenReturn(false)
+ `when`(spatializerRepository.getSpatialAudioCompatibleDevices())
+ .thenReturn(listOf(BLE_AUDIO_DEVICE_ATTRIBUTES))
+ `when`(spatializerRepository.isHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES))
+ .thenReturn(false)
+
+ val setting =
+ getLatestValue(underTest.getDeviceSetting(cachedDevice))
+ as DeviceSettingModel.MultiTogglePreference
+
+ assertThat(setting).isNotNull()
+ assertThat(setting.toggles.size).isEqualTo(2)
+ assertThat(setting.state.selectedIndex).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun getDeviceSetting_headTrackingSupported_returnThreeToggles() {
+ testScope.runTest {
+ `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
+ `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
+ `when`(
+ spatializerRepository.isSpatialAudioAvailableForDevice(
+ BLE_AUDIO_DEVICE_ATTRIBUTES))
+ .thenReturn(true)
+ `when`(
+ spatializerRepository.isHeadTrackingAvailableForDevice(
+ BLE_AUDIO_DEVICE_ATTRIBUTES))
+ .thenReturn(true)
+ `when`(spatializerRepository.getSpatialAudioCompatibleDevices())
+ .thenReturn(listOf(BLE_AUDIO_DEVICE_ATTRIBUTES))
+ `when`(spatializerRepository.isHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES))
+ .thenReturn(true)
+
+ val setting =
+ getLatestValue(underTest.getDeviceSetting(cachedDevice))
+ as DeviceSettingModel.MultiTogglePreference
+
+ assertThat(setting).isNotNull()
+ assertThat(setting.toggles.size).isEqualTo(3)
+ assertThat(setting.state.selectedIndex).isEqualTo(2)
+ }
+ }
+
+ @Test
+ fun getDeviceSetting_updateState_enableSpatialAudio() {
+ testScope.runTest {
+ `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
+ `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
+ `when`(
+ spatializerRepository.isSpatialAudioAvailableForDevice(
+ BLE_AUDIO_DEVICE_ATTRIBUTES))
+ .thenReturn(true)
+ `when`(
+ spatializerRepository.isHeadTrackingAvailableForDevice(
+ BLE_AUDIO_DEVICE_ATTRIBUTES))
+ .thenReturn(true)
+ `when`(spatializerRepository.getSpatialAudioCompatibleDevices()).thenReturn(listOf())
+ `when`(spatializerRepository.isHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES))
+ .thenReturn(false)
+
+ val setting =
+ getLatestValue(underTest.getDeviceSetting(cachedDevice))
+ as DeviceSettingModel.MultiTogglePreference
+ setting.updateState(DeviceSettingStateModel.MultiTogglePreferenceState(2))
+ runCurrent()
+
+ assertThat(setting).isNotNull()
+ verify(spatializerRepository, times(1))
+ .addSpatialAudioCompatibleDevice(BLE_AUDIO_DEVICE_ATTRIBUTES)
+ }
+ }
+
+ @Test
+ fun getDeviceSetting_updateState_enableHeadTracking() {
+ testScope.runTest {
+ `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
+ `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
+ `when`(
+ spatializerRepository.isSpatialAudioAvailableForDevice(
+ BLE_AUDIO_DEVICE_ATTRIBUTES))
+ .thenReturn(true)
+ `when`(
+ spatializerRepository.isHeadTrackingAvailableForDevice(
+ BLE_AUDIO_DEVICE_ATTRIBUTES))
+ .thenReturn(true)
+ `when`(spatializerRepository.getSpatialAudioCompatibleDevices()).thenReturn(listOf())
+ `when`(spatializerRepository.isHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES))
+ .thenReturn(false)
+
+ val setting =
+ getLatestValue(underTest.getDeviceSetting(cachedDevice))
+ as DeviceSettingModel.MultiTogglePreference
+ setting.updateState(DeviceSettingStateModel.MultiTogglePreferenceState(2))
+ runCurrent()
+
+ assertThat(setting).isNotNull()
+ verify(spatializerRepository, times(1))
+ .addSpatialAudioCompatibleDevice(BLE_AUDIO_DEVICE_ATTRIBUTES)
+ verify(spatializerRepository, times(1))
+ .setHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES, true)
+ }
+ }
+
+ private fun getLatestValue(deviceSettingFlow: Flow): DeviceSettingModel? {
+ var latestValue: DeviceSettingModel? = null
+ deviceSettingFlow.onEach { latestValue = it }.launchIn(testScope.backgroundScope)
+ testScope.runCurrent()
+ return latestValue
+ }
+
+ private companion object {
+ const val BLUETOOTH_ADDRESS = "12:34:56:78:12:34"
+ val BLE_AUDIO_DEVICE_ATTRIBUTES =
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ BLUETOOTH_ADDRESS,
+ )
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
new file mode 100644
index 00000000000..609d7679f16
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2024 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.bluetooth.ui.view
+
+import android.bluetooth.BluetoothAdapter
+import android.content.Context
+import android.graphics.Bitmap
+import android.media.AudioManager
+import androidx.fragment.app.FragmentActivity
+import androidx.preference.Preference
+import androidx.preference.PreferenceManager
+import androidx.preference.PreferenceScreen
+import androidx.test.core.app.ApplicationProvider
+import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
+import com.android.settings.dashboard.DashboardFragment
+import com.android.settings.testutils.FakeFeatureFactory
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
+import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.robolectric.Robolectric
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.shadows.ShadowLooper.shadowMainLooper
+
+@RunWith(RobolectricTestRunner::class)
+class DeviceDetailsFragmentFormatterTest {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var cachedDevice: CachedBluetoothDevice
+ @Mock private lateinit var bluetoothAdapter: BluetoothAdapter
+ @Mock private lateinit var repository: DeviceSettingRepository
+ @Mock private lateinit var spatialAudioInteractor: SpatialAudioInteractor
+
+ private lateinit var fragment: TestFragment
+ private lateinit var underTest: DeviceDetailsFragmentFormatter
+ private lateinit var featureFactory: FakeFeatureFactory
+ private val testScope = TestScope()
+
+ @Before
+ fun setUp() {
+ val context = ApplicationProvider.getApplicationContext()
+ featureFactory = FakeFeatureFactory.setupForTest()
+ `when`(
+ featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository(
+ eq(context), eq(bluetoothAdapter), any()))
+ .thenReturn(repository)
+ `when`(
+ featureFactory.bluetoothFeatureProvider.getSpatialAudioInteractor(
+ eq(context), any(AudioManager::class.java), any()))
+ .thenReturn(spatialAudioInteractor)
+ val fragmentActivity = Robolectric.setupActivity(FragmentActivity::class.java)
+ assertThat(fragmentActivity.applicationContext).isNotNull()
+ fragment = TestFragment(context)
+ fragmentActivity.supportFragmentManager.beginTransaction().add(fragment, null).commit()
+ shadowMainLooper().idle()
+
+ fragment.preferenceScreen.run {
+ addPreference(Preference(context).apply { key = "bluetooth_device_header" })
+ addPreference(Preference(context).apply { key = "action_buttons" })
+ addPreference(Preference(context).apply { key = "keyboard_settings" })
+ }
+
+ underTest =
+ DeviceDetailsFragmentFormatterImpl(context, fragment, bluetoothAdapter, cachedDevice)
+ }
+
+ @Test
+ fun getVisiblePreferenceKeysForMainPage_hasConfig_returnList() {
+ testScope.runTest {
+ `when`(repository.getDeviceSettingsConfig(cachedDevice))
+ .thenReturn(
+ DeviceSettingConfigModel(
+ listOf(
+ DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER,
+ "bluetooth_device_header"),
+ DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, "action_buttons"),
+ ),
+ listOf(),
+ "footer"))
+
+ val keys = underTest.getVisiblePreferenceKeysForMainPage()
+
+ assertThat(keys).containsExactly("bluetooth_device_header", "action_buttons")
+ }
+ }
+
+ @Test
+ fun getVisiblePreferenceKeysForMainPage_noConfig_returnNull() {
+ testScope.runTest {
+ `when`(repository.getDeviceSettingsConfig(cachedDevice)).thenReturn(null)
+
+ val keys = underTest.getVisiblePreferenceKeysForMainPage()
+
+ assertThat(keys).isNull()
+ }
+ }
+
+ @Test
+ fun updateLayout_configIsNull_notChange() {
+ testScope.runTest {
+ `when`(repository.getDeviceSettingsConfig(cachedDevice)).thenReturn(null)
+
+ underTest.updateLayout()
+
+ assertThat(getDisplayedPreferences().map { it.key })
+ .containsExactly("bluetooth_device_header", "action_buttons", "keyboard_settings")
+ }
+ }
+
+ @Test
+ fun updateLayout_itemsNotInConfig_hide() {
+ testScope.runTest {
+ `when`(repository.getDeviceSettingsConfig(cachedDevice))
+ .thenReturn(
+ DeviceSettingConfigModel(
+ listOf(
+ DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER,
+ "bluetooth_device_header"),
+ DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
+ "keyboard_settings"),
+ ),
+ listOf(),
+ "footer"))
+
+ underTest.updateLayout()
+
+ assertThat(getDisplayedPreferences().map { it.key })
+ .containsExactly("bluetooth_device_header", "keyboard_settings")
+ }
+ }
+
+ @Test
+ fun updateLayout_newItems_displayNewItems() {
+ testScope.runTest {
+ `when`(repository.getDeviceSettingsConfig(cachedDevice))
+ .thenReturn(
+ DeviceSettingConfigModel(
+ listOf(
+ DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER,
+ "bluetooth_device_header"),
+ DeviceSettingConfigItemModel.AppProvidedItem(
+ DeviceSettingId.DEVICE_SETTING_ID_ANC),
+ DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
+ "keyboard_settings"),
+ ),
+ listOf(),
+ "footer"))
+ `when`(repository.getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_ANC))
+ .thenReturn(
+ flowOf(
+ DeviceSettingModel.MultiTogglePreference(
+ cachedDevice,
+ DeviceSettingId.DEVICE_SETTING_ID_ANC,
+ "title",
+ toggles =
+ listOf(
+ ToggleModel(
+ "", DeviceSettingIcon.BitmapIcon(
+ Bitmap.createBitmap(
+ 1,
+ 1,
+ Bitmap.Config.ARGB_8888
+ )
+ )
+ )
+ ),
+ isActive = true,
+ state = DeviceSettingStateModel.MultiTogglePreferenceState(0),
+ isAllowedChangingState = true,
+ updateState = {})))
+
+ underTest.updateLayout()
+
+ assertThat(getDisplayedPreferences().map { it.key })
+ .containsExactly(
+ "bluetooth_device_header",
+ "DEVICE_SETTING_${DeviceSettingId.DEVICE_SETTING_ID_ANC}",
+ "keyboard_settings")
+ }
+ }
+
+ private fun getDisplayedPreferences(): List {
+ val prefs = mutableListOf()
+ for (i in 0..()
+ featureFactory = FakeFeatureFactory.setupForTest()
+ `when`(
+ featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository(
+ eq(context), eq(bluetoothAdapter), any()))
+ .thenReturn(repository)
+
+ underTest =
+ BluetoothDeviceDetailsViewModel(repository, spatialAudioInteractor, cachedDevice)
+ }
+
+ @Test
+ fun getItems_returnConfigMainItems() {
+ testScope.runTest {
+ `when`(repository.getDeviceSettingsConfig(cachedDevice))
+ .thenReturn(
+ DeviceSettingConfigModel(
+ listOf(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2), listOf(), "footer"))
+
+ val keys = underTest.getItems()
+
+ assertThat(keys).containsExactly(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2)
+ }
+ }
+
+ @Test
+ fun getDeviceSetting_returnRepositoryResponse() {
+ testScope.runTest {
+ val remoteSettingId1 = 10001
+ val pref = buildMultiTogglePreference(remoteSettingId1)
+ `when`(repository.getDeviceSettingsConfig(cachedDevice))
+ .thenReturn(
+ DeviceSettingConfigModel(
+ listOf(
+ BUILTIN_SETTING_ITEM_1,
+ buildRemoteSettingItem(remoteSettingId1),
+ ),
+ listOf(),
+ "footer"))
+ `when`(repository.getDeviceSetting(cachedDevice, remoteSettingId1))
+ .thenReturn(flowOf(pref))
+
+ var deviceSetting: DeviceSettingModel? = null
+ underTest
+ .getDeviceSetting(cachedDevice, remoteSettingId1)
+ .onEach { deviceSetting = it }
+ .launchIn(testScope.backgroundScope)
+ runCurrent()
+
+ assertThat(deviceSetting).isSameInstanceAs(pref)
+ verify(repository, times(1)).getDeviceSetting(cachedDevice, remoteSettingId1)
+ }
+ }
+
+ @Test
+ fun getDeviceSetting_spatialAudio_returnSpatialAudioInteractorResponse() {
+ testScope.runTest {
+ val pref =
+ buildMultiTogglePreference(
+ DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE)
+ `when`(repository.getDeviceSettingsConfig(cachedDevice))
+ .thenReturn(
+ DeviceSettingConfigModel(
+ listOf(
+ BUILTIN_SETTING_ITEM_1,
+ buildRemoteSettingItem(
+ DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE),
+ ),
+ listOf(),
+ "footer"))
+ `when`(spatialAudioInteractor.getDeviceSetting(cachedDevice)).thenReturn(flowOf(pref))
+
+ var deviceSetting: DeviceSettingModel? = null
+ underTest
+ .getDeviceSetting(
+ cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE)
+ .onEach { deviceSetting = it }
+ .launchIn(testScope.backgroundScope)
+ runCurrent()
+
+ assertThat(deviceSetting).isSameInstanceAs(pref)
+ verify(spatialAudioInteractor, times(1)).getDeviceSetting(cachedDevice)
+ }
+ }
+
+ @Test
+ fun getLayout_builtinDeviceSettings() {
+ testScope.runTest {
+ `when`(repository.getDeviceSettingsConfig(cachedDevice))
+ .thenReturn(
+ DeviceSettingConfigModel(
+ listOf(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2), listOf(), "footer"))
+
+ val layout = underTest.getLayout()!!
+
+ assertThat(getLatestLayout(layout))
+ .isEqualTo(
+ listOf(
+ listOf(DeviceSettingId.DEVICE_SETTING_ID_HEADER),
+ listOf(DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS)))
+ }
+ }
+
+ @Test
+ fun getLayout_remoteDeviceSettings() {
+ val remoteSettingId1 = 10001
+ val remoteSettingId2 = 10002
+ val remoteSettingId3 = 10003
+ testScope.runTest {
+ `when`(repository.getDeviceSettingsConfig(cachedDevice))
+ .thenReturn(
+ DeviceSettingConfigModel(
+ listOf(
+ BUILTIN_SETTING_ITEM_1,
+ buildRemoteSettingItem(remoteSettingId1),
+ buildRemoteSettingItem(remoteSettingId2),
+ buildRemoteSettingItem(remoteSettingId3),
+ ),
+ listOf(),
+ "footer"))
+ `when`(repository.getDeviceSetting(cachedDevice, remoteSettingId1))
+ .thenReturn(flowOf(buildMultiTogglePreference(remoteSettingId1)))
+ `when`(repository.getDeviceSetting(cachedDevice, remoteSettingId2))
+ .thenReturn(flowOf(buildMultiTogglePreference(remoteSettingId2)))
+ `when`(repository.getDeviceSetting(cachedDevice, remoteSettingId3))
+ .thenReturn(flowOf(buildActionSwitchPreference(remoteSettingId3)))
+
+ val layout = underTest.getLayout()!!
+
+ assertThat(getLatestLayout(layout))
+ .isEqualTo(
+ listOf(
+ listOf(DeviceSettingId.DEVICE_SETTING_ID_HEADER),
+ listOf(remoteSettingId1, remoteSettingId2),
+ listOf(remoteSettingId3),
+ ))
+ }
+ }
+
+ private fun getLatestLayout(layout: DeviceSettingLayout): List> {
+ var latestLayout = MutableList(layout.rows.size) { emptyList() }
+ for (i in layout.rows.indices) {
+ layout.rows[i]
+ .settingIds
+ .onEach { latestLayout[i] = it }
+ .launchIn(testScope.backgroundScope)
+ }
+
+ testScope.runCurrent()
+ return latestLayout.filter { !it.isEmpty() }.toList()
+ }
+
+ private fun buildMultiTogglePreference(settingId: Int) =
+ DeviceSettingModel.MultiTogglePreference(
+ cachedDevice,
+ settingId,
+ "title",
+ toggles =
+ listOf(
+ ToggleModel(
+ "toggle1",
+ DeviceSettingIcon.BitmapIcon(
+ Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)))),
+ isActive = true,
+ state = DeviceSettingStateModel.MultiTogglePreferenceState(0),
+ isAllowedChangingState = true,
+ updateState = {})
+
+ private fun buildActionSwitchPreference(settingId: Int) =
+ DeviceSettingModel.ActionSwitchPreference(cachedDevice, settingId, "title")
+
+ private fun buildRemoteSettingItem(settingId: Int) =
+ DeviceSettingConfigItemModel.AppProvidedItem(settingId)
+
+ private companion object {
+ val BUILTIN_SETTING_ITEM_1 =
+ DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER, "bluetooth_device_header")
+ val BUILDIN_SETTING_ITEM_2 =
+ DeviceSettingConfigItemModel.BuiltinItem(
+ DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, "action_buttons")
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java
index 01611788e49..59021a78cda 100644
--- a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java
@@ -872,6 +872,17 @@ public class NetworkProviderSettingsTest {
verify(mWifiEntry, never()).getKey();
}
+ @Test
+ public void launchNetworkDetailsFragment_wifiEntryIsNotSaved_ignoreWifiEntry() {
+ when(mWifiEntry.isSaved()).thenReturn(false);
+ LongPressWifiEntryPreference preference =
+ mNetworkProviderSettings.createLongPressWifiEntryPreference(mWifiEntry);
+
+ mNetworkProviderSettings.launchNetworkDetailsFragment(preference);
+
+ verify(mWifiEntry, never()).getKey();
+ }
+
@Implements(PreferenceFragmentCompat.class)
public static class ShadowPreferenceFragmentCompat {
diff --git a/tests/robotests/src/com/android/settings/notification/modes/IconOptionsProviderImplTest.java b/tests/robotests/src/com/android/settings/notification/modes/IconOptionsProviderImplTest.java
index a9bbb4792ef..f0109d69223 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/IconOptionsProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/IconOptionsProviderImplTest.java
@@ -34,7 +34,7 @@ import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class IconOptionsProviderImplTest {
- private static final int EXPECTED_NUMBER_OF_ICON_OPTIONS = 20;
+ private static final int EXPECTED_NUMBER_OF_ICON_OPTIONS = 40;
@Test
public void iconResources_correctResources() {
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
index 9263ffdb8c7..29e9cf9db07 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
@@ -34,10 +34,12 @@ import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.app.Flags;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.UserInfo;
+import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
@@ -102,11 +104,12 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
mContext = RuntimeEnvironment.application;
CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService();
mPreference = new TestableCircularIconsPreference(mContext);
-
when(mApplicationsState.newSession(any(), any())).thenReturn(mSession);
+
mController = new ZenModeAppsLinkPreferenceController(
mContext, "controller_key", mock(Fragment.class), mApplicationsState,
- mZenModesBackend, mHelperBackend);
+ mZenModesBackend, mHelperBackend,
+ /* appIconRetriever= */ appInfo -> new ColorDrawable());
// Ensure the preference view is bound & measured (needed to add child ImageViews).
View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
@@ -163,7 +166,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
assertThat(launcherIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo("com.android.settings.notification.modes.ZenModeAppsFragment");
assertThat(launcherIntent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
- -1)).isEqualTo(0);
+ -1)).isEqualTo(SettingsEnums.ZEN_PRIORITY_MODE);
Bundle bundle = launcherIntent.getBundleExtra(
SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
@@ -295,6 +298,89 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
verify(mSession, times(2)).rebuild(any(), any(), eq(false));
}
+ @Test
+ public void updateState_noneToPriority_loadsBypassingAppsAndListensForChanges() {
+ ZenMode zenModeWithNone = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(false).build())
+ .build();
+ ZenMode zenModeWithPriority = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(true).build())
+ .build();
+ ArrayList appEntries = new ArrayList<>();
+ appEntries.add(createAppEntry("test", mContext.getUserId()));
+ when(mHelperBackend.getPackagesBypassingDnd(mContext.getUserId(), false))
+ .thenReturn(List.of("test"));
+
+ mController.updateState(mPreference, zenModeWithNone);
+
+ assertThat(mPreference.getLoadedIcons().icons()).hasSize(0);
+ verifyNoMoreInteractions(mApplicationsState);
+ verifyNoMoreInteractions(mSession);
+
+ mController.updateState(mPreference, zenModeWithPriority);
+
+ verify(mApplicationsState).newSession(any(), any());
+ verify(mSession).rebuild(any(), any(), anyBoolean());
+ mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
+ assertThat(mPreference.getLoadedIcons().icons()).hasSize(1);
+ }
+
+ @Test
+ public void updateState_priorityToNone_clearsBypassingAppsAndStopsListening() {
+ ZenMode zenModeWithNone = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(false).build())
+ .build();
+ ZenMode zenModeWithPriority = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(true).build())
+ .build();
+ ArrayList appEntries = new ArrayList<>();
+ appEntries.add(createAppEntry("test", mContext.getUserId()));
+ when(mHelperBackend.getPackagesBypassingDnd(mContext.getUserId(), false))
+ .thenReturn(List.of("test"));
+
+ mController.updateState(mPreference, zenModeWithPriority);
+
+ verify(mApplicationsState).newSession(any(), any());
+ verify(mSession).rebuild(any(), any(), anyBoolean());
+ mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
+ assertThat(mPreference.getLoadedIcons().icons()).hasSize(1);
+
+ mController.updateState(mPreference, zenModeWithNone);
+
+ assertThat(mPreference.getLoadedIcons().icons()).hasSize(0);
+ verify(mSession).deactivateSession();
+ verifyNoMoreInteractions(mSession);
+ verifyNoMoreInteractions(mApplicationsState);
+
+ // An errant callback (triggered by onResume and received asynchronously after
+ // updateState()) is ignored.
+ mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
+
+ assertThat(mPreference.getLoadedIcons().icons()).hasSize(0);
+ }
+
+ @Test
+ public void updateState_priorityToNoneToPriority_restartsListening() {
+ ZenMode zenModeWithNone = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(false).build())
+ .build();
+ ZenMode zenModeWithPriority = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(true).build())
+ .build();
+
+ mController.updateState(mPreference, zenModeWithPriority);
+ verify(mApplicationsState).newSession(any(), any());
+ verify(mSession).rebuild(any(), any(), anyBoolean());
+
+ mController.updateState(mPreference, zenModeWithNone);
+ verifyNoMoreInteractions(mApplicationsState);
+ verify(mSession).deactivateSession();
+
+ mController.updateState(mPreference, zenModeWithPriority);
+ verifyNoMoreInteractions(mApplicationsState);
+ verify(mSession).activateSession();
+ }
+
@Test
public void testNoCrashIfAppsReadyBeforeRuleAvailable() {
mController.mAppSessionCallbacks.onLoadEntriesCompleted();
diff --git a/tests/robotests/src/com/android/settings/users/UserCapabilitiesTest.java b/tests/robotests/src/com/android/settings/users/UserCapabilitiesTest.java
index a47703c07c3..bec49e1933b 100644
--- a/tests/robotests/src/com/android/settings/users/UserCapabilitiesTest.java
+++ b/tests/robotests/src/com/android/settings/users/UserCapabilitiesTest.java
@@ -80,6 +80,17 @@ public class UserCapabilitiesTest {
assertThat(userCapabilities.mDisallowSwitchUser).isFalse();
}
+ @Test
+ public void changeAdminStatus_updateUserCapabilities_mIsAdminGetsUpdated() {
+ mUserManager.setIsAdminUser(false);
+ UserCapabilities userCapabilities = UserCapabilities.create(mContext);
+ assertThat(userCapabilities.isAdmin()).isFalse();
+
+ mUserManager.setIsAdminUser(true);
+ userCapabilities.updateAddUserCapabilities(mContext);
+ assertThat(userCapabilities.mIsAdmin).isTrue();
+ }
+
@Test
public void userSwitchEnabled_off() {
mUserManager.setUserSwitcherEnabled(false);
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt
index 4137de494bc..0cbfe02dd42 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt
@@ -17,20 +17,21 @@
package com.android.settings.network.telephony
import android.content.Context
-import android.telephony.AccessNetworkConstants
-import android.telephony.NetworkRegistrationInfo
-import android.telephony.ServiceState
-import android.telephony.TelephonyManager
+import android.os.PersistableBundle
+import android.telephony.*
+import android.telephony.satellite.SatelliteManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.network.telephony.scan.NetworkScanRepositoryTest
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class NetworkSelectRepositoryTest {
@@ -49,8 +50,16 @@ class NetworkSelectRepositoryTest {
on { serviceState } doReturn mockServiceState
}
+ private val mockSatelliteManager = mock {
+ on { getSatellitePlmnsForCarrier(anyInt()) } doReturn SatellitePlmns
+ }
+
+ private var mockCarrierConfigManager = mock()
+
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
+ on { getSystemService(SatelliteManager::class.java) } doReturn mockSatelliteManager
+ on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager
}
private val repository = NetworkSelectRepository(context, SUB_ID)
@@ -105,6 +114,14 @@ class NetworkSelectRepositoryTest {
on { forbiddenPlmns } doReturn arrayOf(FORBIDDEN_PLMN)
}
+ val config = PersistableBundle()
+ config.putBoolean(
+ CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL,
+ false)
+ whenever(mockCarrierConfigManager.getConfigForSubId(
+ SUB_ID, CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL))
+ .thenReturn(config)
+
val info = repository.getNetworkRegistrationInfo()
assertThat(info).isEqualTo(
@@ -115,9 +132,76 @@ class NetworkSelectRepositoryTest {
)
}
+ @Test
+ fun getNetworkRegistrationInfo_filterSatellitePlmn() {
+
+ val info1 = createTestNetworkRegistrationInfo("310", "260")
+ val info2 = createTestNetworkRegistrationInfo("310", "261")
+ val satelliteInfo = createTestNetworkRegistrationInfo(satelliteMcc, satelliteMnc)
+ val registrationInfos = listOf(info1, info2, satelliteInfo)
+ val filteredRegistrationInfos = listOf(info1, info2)
+
+ mockServiceState.stub {
+ on {
+ getNetworkRegistrationInfoListForTransportType(
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+ )
+ } doReturn registrationInfos
+ }
+ mockTelephonyManager.stub {
+ on { forbiddenPlmns } doReturn arrayOf(FORBIDDEN_PLMN)
+ }
+
+ // disable satellite plmn filter
+ var config = PersistableBundle()
+ config.putBoolean(
+ CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL,
+ false)
+ whenever(mockCarrierConfigManager.getConfigForSubId(
+ SUB_ID, CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL))
+ .thenReturn(config)
+
+ var infoList = repository.getNetworkRegistrationInfo()
+
+ assertThat(infoList).isEqualTo(
+ NetworkSelectRepository.NetworkRegistrationAndForbiddenInfo(
+ networkList = registrationInfos,
+ forbiddenPlmns = listOf(FORBIDDEN_PLMN),
+ )
+ )
+
+ // enable satellite plmn filter
+ config = PersistableBundle()
+ config.putBoolean(
+ CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL,
+ true)
+ whenever(mockCarrierConfigManager.getConfigForSubId(
+ SUB_ID, CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL))
+ .thenReturn(config)
+
+ infoList = repository.getNetworkRegistrationInfo()
+
+ assertThat(infoList).isEqualTo(
+ NetworkSelectRepository.NetworkRegistrationAndForbiddenInfo(
+ networkList = filteredRegistrationInfos,
+ forbiddenPlmns = listOf(FORBIDDEN_PLMN),
+ )
+ )
+ }
+
private companion object {
const val SUB_ID = 1
val NetworkRegistrationInfos = listOf(NetworkRegistrationInfo.Builder().build())
const val FORBIDDEN_PLMN = "Forbidden PLMN"
+ const val satelliteMcc = "310"
+ const val satelliteMnc = "810"
+ val SatellitePlmns = listOf(satelliteMcc + satelliteMnc)
+
+ fun createTestNetworkRegistrationInfo(mcc: String, mnc: String): NetworkRegistrationInfo {
+ val cellInfo = CellIdentityLte(0, 0, 0, 0, IntArray(2) { 0 },
+ 0, mcc, mnc, "", "", emptyList(), null)
+
+ return NetworkRegistrationInfo.Builder().setCellIdentity(cellInfo).build()
+ }
}
}
diff --git a/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java b/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java
index 326627a6247..34878e1ef2c 100644
--- a/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -32,6 +33,7 @@ import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.Flags;
import android.os.Looper;
import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -193,6 +195,7 @@ public class BuildNumberPreferenceControllerTest {
@Test
@UiThreadTest
+ @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS)
public void onActivityResult_confirmPasswordRequestCompleted_enableDevPref() {
when(mUserManager.isAdminUser()).thenReturn(true);
@@ -206,7 +209,6 @@ public class BuildNumberPreferenceControllerTest {
}
@Test
- @UiThreadTest
@RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
public void onActivityResult_confirmPasswordRequestCompleted_launchBiometricPrompt() {
when(mUserManager.isAdminUser()).thenReturn(true);
@@ -225,6 +227,45 @@ public class BuildNumberPreferenceControllerTest {
eq(BuildNumberPreferenceController.REQUEST_IDENTITY_CHECK_FOR_DEV_PREF));
}
+ @Test
+ @UiThreadTest
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void onActivityResult_confirmPasswordRequestCompleted_mandatoryBiometricsError() {
+ when(mUserManager.isAdminUser()).thenReturn(true);
+ when(mBiometricManager.canAuthenticate(mContext.getUserId(),
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+ .thenReturn(BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE);
+
+ final boolean activityResultHandled = mController.onActivityResult(
+ BuildNumberPreferenceController.REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF,
+ Activity.RESULT_OK,
+ null);
+
+ assertThat(activityResultHandled).isTrue();
+ verify(mFragment, never()).startActivityForResult(any(),
+ eq(BuildNumberPreferenceController.REQUEST_IDENTITY_CHECK_FOR_DEV_PREF));
+ }
+
+ @Test
+ @UiThreadTest
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void onActivityResult_confirmPasswordRequestCompleted_lockoutError() {
+ when(mUserManager.isAdminUser()).thenReturn(true);
+ when(mBiometricManager.canAuthenticate(mContext.getUserId(),
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+ .thenReturn(BiometricManager.BIOMETRIC_ERROR_LOCKOUT);
+
+ final boolean activityResultHandled = mController.onActivityResult(
+ BuildNumberPreferenceController.REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF,
+ Activity.RESULT_OK,
+ null);
+
+ assertThat(activityResultHandled).isTrue();
+ verify(mFragment, never()).startActivityForResult(any(),
+ eq(BuildNumberPreferenceController.REQUEST_IDENTITY_CHECK_FOR_DEV_PREF));
+ assertThat(DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)).isFalse();
+ }
+
@Test
public void onActivityResult_confirmBiometricAuthentication_enableDevPref() {
when(mUserManager.isAdminUser()).thenReturn(true);