diff --git a/Android.bp b/Android.bp
index 150bdaf84a1..c0a4c7c24f2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -139,6 +139,7 @@ android_library {
"aconfig_settings_flags",
"aconfig_settingslib_flags",
"android.app.flags-aconfig",
+ "android.app.supervision.flags-aconfig",
"android.provider.flags-aconfig",
"android.security.flags-aconfig",
"android.view.contentprotection.flags-aconfig",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8f5699c7572..90119a9a57b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2817,6 +2817,15 @@
+
+
+
+
+
+
+
-
+ android:layout_gravity="center_horizontal"
+ android:gravity="center_vertical"
+ android:paddingEnd="36dp">
+
+
+
+
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
index 2c83a27e557..4d0f7abee5d 100644
--- a/res/values-night/colors.xml
+++ b/res/values-night/colors.xml
@@ -94,8 +94,5 @@
@android:color/system_accent1_100
@android:color/system_accent1_100
-
-
- @android:color/system_on_primary_container_dark
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index e653c63a507..80c3e8d2c4b 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -1059,6 +1059,20 @@
- either_charging_or_docked
+
+ - @string/when_to_show_hubmode_never
+ - @string/when_to_show_hubmode_charging
+ - @string/when_to_show_hubmode_charging_and_upright
+ - @string/when_to_show_hubmode_docked
+
+
+
+ - never
+ - while_charging
+ - while_charging_and_upright
+ - while_docked
+
+
- @string/zen_mode_from_anyone
- @string/zen_mode_from_contacts
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 3201dca98ea..9c9a29c79f9 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -64,8 +64,8 @@
@color/homepage_pink_bg
@color/homepage_pink_fg
@color/homepage_pink_bg
- @color/homepage_pink_fg
- @color/homepage_pink_bg
+ @color/homepage_orange_fg
+ @color/homepage_orange_bg
@color/homepage_orange_fg
@color/homepage_orange_bg
@color/homepage_orange_fg
@@ -275,8 +275,4 @@
@android:color/system_accent1_800
@android:color/system_accent1_800
@android:color/system_neutral2_50
-
-
- @android:color/system_on_primary_dark
- @android:color/system_outline_light
diff --git a/res/values/config.xml b/res/values/config.xml
index c20f08035c2..9d2e43df985 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -810,13 +810,23 @@
-
+
- @layout/accessibility_text_reading_preview_app_grid
- @layout/screen_zoom_preview_1
- @layout/accessibility_text_reading_preview_mail_content
+
+
+ - @string/preview_pager_home_content_description
+ - @string/preview_pager_message_content_description
+ - @string/preview_pager_email_content_description
+
+
com.android.vending
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 79020f903d0..0f92d31fa20 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -78,6 +78,12 @@
Preview
+
+ Home screen preview
+
+ Message preview
+
+ Email preview
QR code
@@ -497,7 +503,7 @@
https://support.google.com/android?p=per_language_app_settings
- Change region to %s ?
+ Change region to %s?
Your device will keep %s as a system language
@@ -506,7 +512,7 @@
%1$s will replace %2$s in your preferred languages
- Change system language to %s ?
+ Change system language to %s?
Add %s to preferred languages?
@@ -547,7 +553,7 @@
More language settings
- Change region to %1$s ?
+ Change region to %1$s?
Your device will keep %1$s as a system language
@@ -833,6 +839,8 @@
Face added
Setup needed
+
+ Add face
Face
@@ -1006,6 +1014,8 @@
}
Setup needed
+
+ Add fingerprint
Set up your fingerprint
@@ -1771,6 +1781,8 @@
None
+
+ Add PIN, pattern, password, or swipe
Swipe
@@ -3655,6 +3667,21 @@
Communal
Communal settings
+
+ Hub mode
+
+ Widgets on lock screen
+
+
+ When to automatically show
+
+ Never
+
+ While charging
+
+ While charging and upright
+
+ While docked
@@ -3897,6 +3924,18 @@
Bluetooth tethering
+
+
+ IP address
+
+ MAC address
+
+ Transit link speed
+
+ Receive link speed
+
+ Network usage
+
Ethernet tethering
@@ -5691,6 +5730,9 @@
Longer
Auto click time
+
+
+ Use autoclick
Autoclick shortcut
@@ -14309,5 +14351,7 @@ Data usage charges may apply.
Allow all sites
+
+ Enter supervision PIN
%1$s animation
diff --git a/res/xml/accessibility_autoclick_settings.xml b/res/xml/accessibility_autoclick_settings.xml
index 3878e6c5c9b..02f53c0e4bb 100644
--- a/res/xml/accessibility_autoclick_settings.xml
+++ b/res/xml/accessibility_autoclick_settings.xml
@@ -30,6 +30,11 @@
settings:searchable="false"
settings:lottie_rawRes="@drawable/accessibility_dwell"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/mouse_settings.xml b/res/xml/mouse_settings.xml
index ec1c39d31d2..98026bdcad2 100644
--- a/res/xml/mouse_settings.xml
+++ b/res/xml/mouse_settings.xml
@@ -27,7 +27,7 @@
android:order="10"
settings:controller="com.android.settings.inputmethod.MousePointerAccelerationPreferenceController" />
-
-
+
-
+
-
-
-
-
-
+
diff --git a/res/xml/touchpad_settings.xml b/res/xml/touchpad_settings.xml
index 7369d485cbb..c71790ee93a 100644
--- a/res/xml/touchpad_settings.xml
+++ b/res/xml/touchpad_settings.xml
@@ -61,7 +61,7 @@
settings:controller="com.android.settings.inputmethod.TouchpadAccelerationPreferenceController"
android:order="38"/>
-
+
+
diff --git a/res/xml/widgets_on_lockscreen_settings.xml b/res/xml/widgets_on_lockscreen_settings.xml
new file mode 100644
index 00000000000..056df63ebd4
--- /dev/null
+++ b/res/xml/widgets_on_lockscreen_settings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
diff --git a/src/com/android/settings/accessibility/TextReadingPreviewController.java b/src/com/android/settings/accessibility/TextReadingPreviewController.java
index 99f1f3fa0c3..e268aaa981f 100644
--- a/src/com/android/settings/accessibility/TextReadingPreviewController.java
+++ b/src/com/android/settings/accessibility/TextReadingPreviewController.java
@@ -104,11 +104,13 @@ class TextReadingPreviewController extends BasePreferenceController implements
final boolean isLayoutRtl =
origConfig.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
final int[] previewSamples = getPreviewSampleLayouts(mContext);
+ final int[] previewContentDescriptions = getPreviewSampleContentDescriptions(mContext);
final PreviewPagerAdapter pagerAdapter = new PreviewPagerAdapter(mContext, isLayoutRtl,
previewSamples, createConfig(origConfig));
mPreviewPreference.setPreviewAdapter(pagerAdapter);
mPreviewPreference.setCurrentItem(
isLayoutRtl ? previewSamples.length - 1 : FRAME_INITIAL_INDEX);
+ mPreviewPreference.setContentDescription(previewContentDescriptions);
final int initialPagerIndex =
mLastFontProgress * mDisplaySizeData.getValues().size() + mLastDisplayProgress;
@@ -188,6 +190,20 @@ class TextReadingPreviewController extends BasePreferenceController implements
return previewSamples;
}
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static int[] getPreviewSampleContentDescriptions(Context context) {
+ TypedArray typedArray = context.getResources().obtainTypedArray(
+ R.array.config_text_reading_preview_content_descriptions);
+ int previewCount = typedArray.length();
+ int[] previewContentDescriptions = new int[previewCount];
+ for (int i = 0; i < previewCount; i++) {
+ previewContentDescriptions[i] =
+ typedArray.getResourceId(i, R.string.preview_pager_content_description);
+ }
+ typedArray.recycle();
+ return previewContentDescriptions;
+ }
+
private int getPagerIndex() {
final int displayDataSize = mDisplaySizeData.getValues().size();
final int fontSizeProgress = mFontSizePreference.getProgress();
diff --git a/src/com/android/settings/accessibility/TextReadingPreviewPreference.java b/src/com/android/settings/accessibility/TextReadingPreviewPreference.java
index 5c866d9d42c..a4676baf58a 100644
--- a/src/com/android/settings/accessibility/TextReadingPreviewPreference.java
+++ b/src/com/android/settings/accessibility/TextReadingPreviewPreference.java
@@ -43,28 +43,11 @@ public class TextReadingPreviewPreference extends Preference {
private int mCurrentItem;
private int mLastLayerIndex;
private PreviewPagerAdapter mPreviewAdapter;
+ private int[] mContentDescriptions;
private int mLayoutMinHorizontalPadding = 0;
private int mBackgroundMinHorizontalPadding = 0;
- private final ViewPager.OnPageChangeListener mPageChangeListener =
- new ViewPager.OnPageChangeListener() {
- @Override
- public void onPageScrolled(int i, float v, int i1) {
- // Do nothing
- }
-
- @Override
- public void onPageSelected(int i) {
- mCurrentItem = i;
- }
-
- @Override
- public void onPageScrollStateChanged(int i) {
- // Do nothing
- }
- };
-
TextReadingPreviewPreference(Context context) {
super(context);
init();
@@ -95,7 +78,23 @@ public class TextReadingPreviewPreference extends Preference {
adjustPaddings(previewLayout, backgroundView);
final ViewPager viewPager = (ViewPager) holder.findViewById(R.id.preview_pager);
- viewPager.addOnPageChangeListener(mPageChangeListener);
+ viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+ @Override
+ public void onPageScrolled(int i, float v, int i1) {
+ // Do nothing
+ }
+
+ @Override
+ public void onPageSelected(int i) {
+ mCurrentItem = i;
+ viewPager.setContentDescription(getContext().getString(mContentDescriptions[i]));
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int i) {
+ // Do nothing
+ }
+ });
final DotsPageIndicator pageIndicator =
(DotsPageIndicator) holder.findViewById(R.id.page_indicator);
updateAdapterIfNeeded(viewPager, pageIndicator, mPreviewAdapter);
@@ -122,6 +121,10 @@ public class TextReadingPreviewPreference extends Preference {
viewPager.setCurrentItem(getCurrentItem() + 1));
nextButton.setContentDescription(getContext().getString(
R.string.preview_pager_next_button));
+
+ // Initialize the content description since the OnPageChangeListener#onPageSelected won't
+ // be called during setup.
+ viewPager.setContentDescription(getContext().getString(mContentDescriptions[0]));
}
@Override
@@ -170,6 +173,10 @@ public class TextReadingPreviewPreference extends Preference {
);
}
+ void setContentDescription(int[] stringIds) {
+ mContentDescriptions = stringIds;
+ }
+
void setPreviewAdapter(PreviewPagerAdapter previewAdapter) {
if (previewAdapter != mPreviewAdapter) {
mPreviewAdapter = previewAdapter;
diff --git a/src/com/android/settings/accessibility/ToggleAutoclickMainSwitchPreferenceController.java b/src/com/android/settings/accessibility/ToggleAutoclickMainSwitchPreferenceController.java
new file mode 100644
index 00000000000..1ecdac9eef8
--- /dev/null
+++ b/src/com/android/settings/accessibility/ToggleAutoclickMainSwitchPreferenceController.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
+import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.accessibility.Flags;
+import com.android.settings.R;
+import com.android.settings.core.TogglePreferenceController;
+
+/** The controller to handle main switch to turn on or turn off accessibility autoclick. */
+public class ToggleAutoclickMainSwitchPreferenceController
+ extends TogglePreferenceController {
+
+ private final ContentResolver mContentResolver;
+
+ public ToggleAutoclickMainSwitchPreferenceController(
+ @NonNull Context context, @NonNull String preferenceKey) {
+ super(context, preferenceKey);
+ mContentResolver = context.getContentResolver();
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return Flags.enableAutoclickIndicator() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF) == ON;
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ Settings.Secure.putInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
+ isChecked ? ON : OFF);
+
+ return true;
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ return R.string.menu_key_system;
+ }
+}
diff --git a/src/com/android/settings/applications/appcompat/OWNERS b/src/com/android/settings/applications/appcompat/OWNERS
new file mode 100644
index 00000000000..5ef6ded95f1
--- /dev/null
+++ b/src/com/android/settings/applications/appcompat/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 970984
+# Large Screen Experiences App Compat
+gracielawputri@google.com
+mcarli@google.com
+mariiasand@google.com
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/BiometricSettingsProvider.kt b/src/com/android/settings/biometrics/BiometricSettingsProvider.kt
index 308f24508e3..ee4cf4879c1 100644
--- a/src/com/android/settings/biometrics/BiometricSettingsProvider.kt
+++ b/src/com/android/settings/biometrics/BiometricSettingsProvider.kt
@@ -30,7 +30,7 @@ class BiometricSettingsProvider : ContentProvider() {
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int {
- throw UnsupportedOperationException("query operation not supported currently.")
+ throw UnsupportedOperationException("delete operation not supported currently.")
}
override fun getType(uri: Uri): String? {
diff --git a/src/com/android/settings/biometrics/ParentalControlsUtils.java b/src/com/android/settings/biometrics/ParentalControlsUtils.java
index 04ab1b17596..e9c76e55263 100644
--- a/src/com/android/settings/biometrics/ParentalControlsUtils.java
+++ b/src/com/android/settings/biometrics/ParentalControlsUtils.java
@@ -31,6 +31,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.supervision.SupervisionRestrictionsHelper;
/**
* Utilities for things at the cross-section of biometrics and parental controls. For example,
@@ -59,12 +60,7 @@ public class ParentalControlsUtils {
UserManager.DISALLOW_BIOMETRIC, userHandle);
}
- final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
- final SupervisionManager sm =
- android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()
- ? context.getSystemService(SupervisionManager.class)
- : null;
- return parentConsentRequiredInternal(dpm, sm, modality, userHandle);
+ return parentConsentRequiredInternal(context, modality, userHandle);
}
/**
@@ -74,18 +70,22 @@ public class ParentalControlsUtils {
@Nullable
@VisibleForTesting
static RestrictedLockUtils.EnforcedAdmin parentConsentRequiredInternal(
- @NonNull DevicePolicyManager dpm,
- @Nullable SupervisionManager sm,
+ @NonNull Context context,
@BiometricAuthenticator.Modality int modality,
@NonNull UserHandle userHandle) {
+ final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ final SupervisionManager sm =
+ android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()
+ ? context.getSystemService(SupervisionManager.class)
+ : null;
+
if (!ParentalControlsUtilsInternal.parentConsentRequired(
dpm, sm, modality, userHandle)) {
return null;
}
if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) {
- // Supervision doesn't necessarily have have an admin component.
- return new RestrictedLockUtils.EnforcedAdmin(
- /* component= */ null, UserManager.DISALLOW_BIOMETRIC, userHandle);
+ return SupervisionRestrictionsHelper.createEnforcedAdmin(
+ context, UserManager.DISALLOW_BIOMETRIC, userHandle);
} else {
final ComponentName cn =
ParentalControlsUtilsInternal.getSupervisionComponentName(dpm, userHandle);
diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java
index d2e2a80a406..4e129f0744a 100644
--- a/src/com/android/settings/biometrics/face/FaceSettings.java
+++ b/src/com/android/settings/biometrics/face/FaceSettings.java
@@ -70,6 +70,7 @@ public class FaceSettings extends DashboardFragment {
private static final String TAG = "FaceSettings";
private static final String KEY_TOKEN = "hw_auth_token";
+ private static final String KEY_CONFIRMING_PASSWORD = "confirming_password";
private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock";
private static final String KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED =
"biometrics_successfully_authenticated";
@@ -163,6 +164,7 @@ public class FaceSettings extends DashboardFragment {
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putByteArray(KEY_TOKEN, mToken);
+ outState.putBoolean(KEY_CONFIRMING_PASSWORD, mConfirmingPassword);
}
@Override
@@ -273,6 +275,7 @@ public class FaceSettings extends DashboardFragment {
if (savedInstanceState != null) {
mToken = savedInstanceState.getByteArray(KEY_TOKEN);
+ mConfirmingPassword = savedInstanceState.getBoolean(KEY_CONFIRMING_PASSWORD);
}
if (Flags.biometricsOnboardingEducation()) {
diff --git a/src/com/android/settings/biometrics/face/FaceStatusUtils.java b/src/com/android/settings/biometrics/face/FaceStatusUtils.java
index 302d6773213..ee5277edf7a 100644
--- a/src/com/android/settings/biometrics/face/FaceStatusUtils.java
+++ b/src/com/android/settings/biometrics/face/FaceStatusUtils.java
@@ -98,11 +98,14 @@ public class FaceStatusUtils {
return mContext.getString(
com.android.settingslib.widget.restricted.R.string.disabled_by_admin);
} else {
+ int summaryNoneResId = Flags.biometricsOnboardingEducation()
+ ? R.string.security_settings_face_preference_summary_none_new
+ : R.string.security_settings_face_preference_summary_none;
return mContext.getResources()
.getString(
hasEnrolled()
? R.string.security_settings_face_preference_summary
- : R.string.security_settings_face_preference_summary_none);
+ : summaryNoneResId);
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java
index 1ca564c8e35..b7ca3aa0002 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java
@@ -108,7 +108,9 @@ public class FingerprintStatusUtils {
R.string.security_settings_fingerprint_preference_summary);
} else {
return mContext.getString(
- R.string.security_settings_fingerprint_preference_summary_none);
+ Flags.biometricsOnboardingEducation()
+ ? R.string.security_settings_fingerprint_preference_summary_none_new
+ : R.string.security_settings_fingerprint_preference_summary_none);
}
}
diff --git a/src/com/android/settings/communal/CommunalPreferenceController.java b/src/com/android/settings/communal/CommunalPreferenceController.java
index e44afab90a6..16eb0e9dfc4 100644
--- a/src/com/android/settings/communal/CommunalPreferenceController.java
+++ b/src/com/android/settings/communal/CommunalPreferenceController.java
@@ -39,15 +39,14 @@ public class CommunalPreferenceController extends BasePreferenceController {
* Returns whether communal preferences are available.
*/
public static boolean isAvailable(Context context) {
+ if (com.android.systemui.Flags.glanceableHubV2()) {
+ return false;
+ }
+
if (!Utils.canCurrentUserDream(context)) {
return false;
}
- if (context.getResources().getBoolean(R.bool.config_show_communal_settings)) {
- return true;
- }
-
- return com.android.systemui.Flags.glanceableHubV2()
- && context.getResources().getBoolean(R.bool.config_show_communal_settings_mobile);
+ return context.getResources().getBoolean(R.bool.config_show_communal_settings);
}
}
diff --git a/src/com/android/settings/communal/WhenToStartHubPicker.java b/src/com/android/settings/communal/WhenToStartHubPicker.java
new file mode 100644
index 00000000000..59233257a30
--- /dev/null
+++ b/src/com/android/settings/communal/WhenToStartHubPicker.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.communal;
+
+import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_CHARGING;
+import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_CHARGING_UPRIGHT;
+import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_DOCKED;
+import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_NEVER;
+import static android.provider.Settings.Secure.WHEN_TO_START_GLANCEABLE_HUB;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+import com.android.settings.widget.RadioButtonPickerFragment;
+import com.android.settingslib.widget.CandidateInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Fragment that provides radio buttons to allow the user to choose when the hub should auto-start.
+ */
+public class WhenToStartHubPicker extends RadioButtonPickerFragment {
+ private static final String TAG = "WhenToStartHubPicker";
+ private static final String SHOW_WHILE_CHARGING = "while_charging";
+ private static final String SHOW_WHILE_DOCKED = "while_docked";
+ private static final String SHOW_WHILE_CHARGING_AND_UPRIGHT = "while_charging_and_upright";
+ private static final String SHOW_NEVER = "never";
+
+ private Context mContext;
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+
+ mContext = context;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.when_to_start_hubmode_settings;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.WHEN_TO_SHOW_WIDGETS_ON_LOCKSCREEN;
+ }
+
+ @Override
+ protected List extends CandidateInfo> getCandidates() {
+ final List candidates = new ArrayList<>();
+
+ final String[] entries = entries();
+ final String[] values = keys();
+
+ if (entries == null || entries.length <= 0) return candidates;
+ if (values == null || values.length != entries.length) {
+ throw new IllegalArgumentException("Entries and values must be of the same length.");
+ }
+
+ for (int i = 0; i < entries.length; i++) {
+ candidates.add(new WhenToStartHubCandidateInfo(entries[i], values[i]));
+ }
+
+ return candidates;
+ }
+
+ private String[] entries() {
+ return getResources().getStringArray(R.array.when_to_start_hubmode_entries);
+ }
+
+ private String[] keys() {
+ return getResources().getStringArray(R.array.when_to_start_hubmode_values);
+ }
+
+ @Override
+ protected String getDefaultKey() {
+ final int defaultValue = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_whenToStartHubModeDefault);
+ final int setting = Settings.Secure.getInt(
+ mContext.getContentResolver(), WHEN_TO_START_GLANCEABLE_HUB, defaultValue);
+ return getKeyFromSetting(setting);
+ }
+
+ @Override
+ protected boolean setDefaultKey(String key) {
+ Settings.Secure.putInt(
+ mContext.getContentResolver(),
+ WHEN_TO_START_GLANCEABLE_HUB,
+ getSettingFromPrefKey(key));
+
+ return true;
+ }
+
+ @Override
+ protected void onSelectionPerformed(boolean success) {
+ super.onSelectionPerformed(success);
+
+ getActivity().finish();
+ }
+
+
+ @Settings.Secure.WhenToStartGlanceableHub
+ private static int getSettingFromPrefKey(String key) {
+ switch (key) {
+ case SHOW_WHILE_CHARGING:
+ return GLANCEABLE_HUB_START_CHARGING;
+ case SHOW_WHILE_DOCKED:
+ return GLANCEABLE_HUB_START_DOCKED;
+ case SHOW_WHILE_CHARGING_AND_UPRIGHT:
+ return GLANCEABLE_HUB_START_CHARGING_UPRIGHT;
+ case SHOW_NEVER:
+ default:
+ return GLANCEABLE_HUB_START_NEVER;
+ }
+ }
+
+ private static String getKeyFromSetting(@Settings.Secure.WhenToStartGlanceableHub int setting) {
+ switch (setting) {
+ case GLANCEABLE_HUB_START_CHARGING:
+ return SHOW_WHILE_CHARGING;
+ case GLANCEABLE_HUB_START_DOCKED:
+ return SHOW_WHILE_DOCKED;
+ case GLANCEABLE_HUB_START_CHARGING_UPRIGHT:
+ return SHOW_WHILE_CHARGING_AND_UPRIGHT;
+ case GLANCEABLE_HUB_START_NEVER:
+ default:
+ return SHOW_NEVER;
+ }
+ }
+
+ private static final class WhenToStartHubCandidateInfo extends CandidateInfo {
+ private final String mName;
+ private final String mKey;
+
+ WhenToStartHubCandidateInfo(String title, String value) {
+ super(true);
+
+ mName = title;
+ mKey = value;
+ }
+
+ @Override
+ public CharSequence loadLabel() {
+ return mName;
+ }
+
+ @Override
+ @Nullable
+ public Drawable loadIcon() {
+ return null;
+ }
+
+ @Override
+ public String getKey() {
+ return mKey;
+ }
+ }
+}
diff --git a/src/com/android/settings/communal/WhenToStartHubPreferenceController.java b/src/com/android/settings/communal/WhenToStartHubPreferenceController.java
new file mode 100644
index 00000000000..67931ee7f65
--- /dev/null
+++ b/src/com/android/settings/communal/WhenToStartHubPreferenceController.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.communal;
+
+import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_CHARGING;
+import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_CHARGING_UPRIGHT;
+import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_DOCKED;
+import static android.provider.Settings.Secure.GLANCEABLE_HUB_START_NEVER;
+import static android.provider.Settings.Secure.WHEN_TO_START_GLANCEABLE_HUB;
+
+import android.annotation.StringRes;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.PreferenceControllerMixin;
+
+/**
+ * A preference controller that is responsible for showing the "when to auto start hub" setting in
+ * hub settings.
+ */
+public class WhenToStartHubPreferenceController extends BasePreferenceController implements
+ PreferenceControllerMixin {
+ public WhenToStartHubPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+
+ preference.setSummary(getSummaryResId());
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return mContext.getString(getSummaryResId());
+ }
+
+ @StringRes
+ private int getSummaryResId() {
+ final int setting = Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ WHEN_TO_START_GLANCEABLE_HUB,
+ GLANCEABLE_HUB_START_NEVER);
+
+ switch (setting) {
+ case GLANCEABLE_HUB_START_CHARGING:
+ return R.string.when_to_show_hubmode_charging;
+ case GLANCEABLE_HUB_START_DOCKED:
+ return R.string.when_to_show_hubmode_docked;
+ case GLANCEABLE_HUB_START_CHARGING_UPRIGHT:
+ return R.string.when_to_show_hubmode_charging_and_upright;
+ case GLANCEABLE_HUB_START_NEVER:
+ default:
+ return R.string.when_to_show_hubmode_never;
+ }
+ }
+}
diff --git a/src/com/android/settings/communal/WidgetsOnLockscreenFragment.java b/src/com/android/settings/communal/WidgetsOnLockscreenFragment.java
new file mode 100644
index 00000000000..09a1e4d1c3e
--- /dev/null
+++ b/src/com/android/settings/communal/WidgetsOnLockscreenFragment.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.communal;
+
+import android.app.settings.SettingsEnums;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/**
+ * Fragment that contains settings related to communal hub.
+ */
+@SearchIndexable
+public class WidgetsOnLockscreenFragment extends DashboardFragment {
+ private static final String TAG = "WidgetsOnLockscreenFragment";
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.WIDGETS_ON_LOCK_SCREEN;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.widgets_on_lockscreen_settings;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.widgets_on_lockscreen_settings);
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
index 68cb8226b7b..8050b4056f6 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
@@ -26,9 +26,14 @@ import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.database.ContentObserver;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -87,6 +92,7 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
@Nullable private final LocalBluetoothProfileManager mProfileManager;
@Nullable private final LocalBluetoothLeBroadcast mBroadcast;
@Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant;
+ @Nullable private final ContentResolver mContentResolver;
private final Executor mExecutor;
private final MetricsFeatureProvider mMetricsFeatureProvider;
@Nullable private PreferenceGroup mPreferenceGroup;
@@ -187,6 +193,17 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
@NonNull BluetoothLeBroadcastReceiveState state) {}
};
+ @VisibleForTesting
+ ContentObserver mSettingsObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ Log.d(TAG, "onChange, primary group id has been changed, refresh list");
+ if (mBluetoothDeviceUpdater != null) {
+ mBluetoothDeviceUpdater.refreshPreference();
+ }
+ }
+ };
+
public AudioSharingDevicePreferenceController(Context context) {
super(context, KEY);
mBtManager = Utils.getLocalBtManager(mContext);
@@ -198,6 +215,7 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
mProfileManager == null
? null
: mProfileManager.getLeAudioBroadcastAssistantProfile();
+ mContentResolver = context.getContentResolver();
mExecutor = Executors.newSingleThreadExecutor();
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
}
@@ -217,6 +235,7 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
if (mEventManager == null
|| mAssistant == null
|| mDialogHandler == null
+ || mContentResolver == null
|| mBluetoothDeviceUpdater == null) {
Log.d(TAG, "Skip onStart(), profile is not ready.");
return;
@@ -225,6 +244,10 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
mEventManager.registerCallback(this);
mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
mDialogHandler.registerCallbacks(mExecutor);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast()),
+ false,
+ mSettingsObserver);
mBluetoothDeviceUpdater.registerCallback();
mBluetoothDeviceUpdater.refreshPreference();
mIsAudioModeOngoingCall.set(isAudioModeOngoingCall(mContext));
@@ -245,6 +268,7 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
if (mEventManager == null
|| mAssistant == null
|| mDialogHandler == null
+ || mContentResolver == null
|| mBluetoothDeviceUpdater == null) {
Log.d(TAG, "Skip onStop(), profile is not ready.");
return;
@@ -253,6 +277,7 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
mEventManager.unregisterCallback(this);
mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
mDialogHandler.unregisterCallbacks();
+ mContentResolver.unregisterContentObserver(mSettingsObserver);
mBluetoothDeviceUpdater.unregisterCallback();
});
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
index 6037577162f..a9cca9ddc99 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
@@ -40,6 +40,7 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.LayoutPreference;
@@ -88,8 +89,10 @@ public class AudioStreamHeaderController extends BasePreferenceController
var localSourceState = getLocalSourceState(state);
if (localSourceState == STREAMING) {
updateSummary();
- mAudioStreamsHelper.startMediaService(
- mContext, mBroadcastId, mBroadcastName);
+ if (!Flags.audioStreamMediaServiceByReceiveState()) {
+ mAudioStreamsHelper.startMediaService(
+ mContext, mBroadcastId, mBroadcastName);
+ }
} else if (mHysteresisModeFixAvailable && localSourceState == PAUSED) {
// if source paused, only update the summary
updateSummary();
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java
index ec8d7bcc206..549046f557b 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java
@@ -16,9 +16,13 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
+import static java.util.Collections.emptyList;
+
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -56,11 +60,14 @@ import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.PrivateBroadcastReceiveData;
import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.utils.ThreadUtils;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
public class AudioStreamMediaService extends Service {
@@ -103,7 +110,7 @@ public class AudioStreamMediaService extends Service {
private final PlaybackState.Builder mPlayStateHysteresisBuilder =
new PlaybackState.Builder()
.setState(
- PlaybackState.STATE_STOPPED,
+ PlaybackState.STATE_PAUSED,
STATIC_PLAYBACK_POSITION,
ZERO_PLAYBACK_SPEED)
.addCustomAction(
@@ -122,7 +129,9 @@ public class AudioStreamMediaService extends Service {
private int mLatestPositiveVolume = 25;
private boolean mHysteresisModeFixAvailable;
private int mBroadcastId;
- @Nullable private Map mStateByDevice;
+ @VisibleForTesting
+ @Nullable
+ Map mStateByDevice;
@Nullable private LocalBluetoothManager mLocalBtManager;
@Nullable private AudioStreamsHelper mAudioStreamsHelper;
@Nullable private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
@@ -236,6 +245,19 @@ public class AudioStreamMediaService extends Service {
stopSelf();
return START_NOT_STICKY;
}
+ // TODO(b/398700619): Remove hasExtra check when feasible.
+ if (Flags.audioStreamMediaServiceByReceiveState() && intent.hasExtra(
+ EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA)) {
+ PrivateBroadcastReceiveData data = intent.getParcelableExtra(
+ EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA, PrivateBroadcastReceiveData.class);
+ if (data == null || !PrivateBroadcastReceiveData.Companion.isValid(data)) {
+ Log.w(TAG, "Data is null or invalid. Service will not start.");
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+ getHandler().post(() -> handleIntentData(data));
+ return START_NOT_STICKY;
+ }
getHandler().post(() -> {
mBroadcastId = intent.getIntExtra(BROADCAST_ID, -1);
if (mBroadcastId == -1) {
@@ -258,6 +280,78 @@ public class AudioStreamMediaService extends Service {
return START_NOT_STICKY;
}
+ private void handleIntentData(PrivateBroadcastReceiveData data) {
+ int broadcastId = data.getBroadcastId();
+ BluetoothDevice device = data.getSink();
+ int sourceId = data.getSourceId();
+ var state = data.getState();
+ String programInfo = data.getProgramInfo();
+
+ // Service not running yet.
+ if (mBroadcastId == 0) {
+ Log.d(TAG, "handleIntentData(): sending " + data + " to handleInitialSetup()");
+ handleInitialSetup(broadcastId, device, state, sourceId, programInfo);
+ return;
+ }
+
+ // Service running with a different broadcast id, most likely staled. We have a new
+ // broadcast Id to handle.
+ if (mBroadcastId != broadcastId) {
+ Log.d(TAG, "handleIntentData(): sending " + data + " to handleNewBroadcastId()");
+ handleNewBroadcastId(broadcastId, device, state, sourceId, programInfo);
+ return;
+ }
+
+ // Service running with the same broadcast Id, we have new device joining or a state update.
+ if (mStateByDevice != null && (!mStateByDevice.containsKey(device) || mStateByDevice.get(
+ device) != state)) {
+ Log.d(TAG, "handleIntentData(): sending " + data + " to handleNewDeviceOrState()");
+ handleNewDeviceOrState(device, state, sourceId, programInfo);
+ }
+
+ Log.d(TAG, "handleIntentData(): nothing to update.");
+ }
+
+ private void handleInitialSetup(int broadcastId, BluetoothDevice device,
+ LocalBluetoothLeBroadcastSourceState state, int sourceId, String programInfo) {
+ if (state == DECRYPTION_FAILED) {
+ Log.d(TAG, "handleInitialSetup() : decryption failed. Service will not start.");
+ stopSelf();
+ return;
+ }
+ mBroadcastId = broadcastId;
+ mStateByDevice = new HashMap<>();
+ mStateByDevice.put(device, state);
+ MediaSession.Token token = getOrCreateLocalMediaSession(
+ getBroadcastName(device, sourceId, programInfo));
+ startForeground(NOTIFICATION_ID, buildNotification(token));
+ }
+
+ private void handleNewBroadcastId(int broadcastId, BluetoothDevice device,
+ LocalBluetoothLeBroadcastSourceState state, int sourceId, String programInfo) {
+ if (state == DECRYPTION_FAILED) {
+ Log.d(TAG, "handleNewBroadcastId() : decryption failed. Ignore.");
+ return;
+ }
+ mBroadcastId = broadcastId;
+ mStateByDevice = new HashMap<>();
+ mStateByDevice.put(device, state);
+ updateMediaSessionAndNotify(device, sourceId, programInfo);
+ }
+
+ private void handleNewDeviceOrState(BluetoothDevice device,
+ LocalBluetoothLeBroadcastSourceState state, int sourceId, String programInfo) {
+ if (mStateByDevice != null) {
+ mStateByDevice.put(device, state);
+ }
+ if (getDeviceInValidState().isEmpty()) {
+ Log.d(TAG, "handleNewDeviceOrState() : no device is in valid state. Stop service.");
+ stopSelf();
+ return;
+ }
+ updateMediaSessionAndNotify(device, sourceId, programInfo);
+ }
+
private MediaSession.Token getOrCreateLocalMediaSession(String title) {
if (mLocalSession != null) {
return mLocalSession.getSessionToken();
@@ -288,7 +382,8 @@ public class AudioStreamMediaService extends Service {
}
private String getDeviceName() {
- if (mStateByDevice == null || mStateByDevice.isEmpty() || mLocalBtManager == null) {
+ List validDevices = getDeviceInValidState();
+ if (validDevices.isEmpty() || mLocalBtManager == null) {
return DEFAULT_DEVICE_NAME;
}
@@ -297,8 +392,7 @@ public class AudioStreamMediaService extends Service {
return DEFAULT_DEVICE_NAME;
}
- CachedBluetoothDevice device = manager.findDevice(
- mStateByDevice.keySet().iterator().next());
+ CachedBluetoothDevice device = manager.findDevice(validDevices.getFirst());
return device != null ? device.getName() : DEFAULT_DEVICE_NAME;
}
@@ -320,6 +414,47 @@ public class AudioStreamMediaService extends Service {
return notificationBuilder.build();
}
+ private void updateMediaSessionAndNotify(BluetoothDevice device, int sourceId,
+ String programInfo) {
+ if (mNotificationManager == null || mLocalSession == null) {
+ Log.w(TAG, "mNotificationManager or mLocalSession is null, ignore update.");
+ return;
+ }
+ mLocalSession.setMetadata(new MediaMetadata.Builder().putString(
+ MediaMetadata.METADATA_KEY_TITLE,
+ getBroadcastName(device, sourceId, programInfo)).putLong(
+ MediaMetadata.METADATA_KEY_DURATION, STATIC_PLAYBACK_DURATION).build());
+ mLocalSession.setPlaybackState(getPlaybackState());
+ mNotificationManager.notify(NOTIFICATION_ID,
+ buildNotification(mLocalSession.getSessionToken()));
+ }
+
+ private String getBroadcastName(BluetoothDevice sink, int sourceId, String programInfo) {
+ if (mLeBroadcastAssistant == null || sink == null) {
+ return programInfo;
+ }
+ var metadata = mLeBroadcastAssistant.getSourceMetadata(sink, sourceId);
+ if (metadata == null || metadata.getBroadcastId() != mBroadcastId
+ || metadata.getBroadcastName() == null || metadata.getBroadcastName().isEmpty()) {
+ Log.d(TAG, "getBroadcastName(): source metadata not found, using programInfo: "
+ + programInfo);
+ return programInfo;
+ }
+ return metadata.getBroadcastName();
+ }
+
+ private List getDeviceInValidState() {
+ if (mStateByDevice == null || mStateByDevice.isEmpty()) {
+ Log.w(TAG, "getDeviceInValidState() : mStateByDevice is null or empty!");
+ return emptyList();
+ }
+ if (Flags.audioStreamMediaServiceByReceiveState()) {
+ return mStateByDevice.entrySet().stream().filter(
+ entry -> entry.getValue() != DECRYPTION_FAILED).map(Map.Entry::getKey).toList();
+ }
+ return mStateByDevice.keySet().stream().toList();
+ }
+
@Nullable
@Override
public IBinder onBind(Intent intent) {
@@ -342,6 +477,9 @@ public class AudioStreamMediaService extends Service {
@Override
public void onReceiveStateChanged(
BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
+ if (Flags.audioStreamMediaServiceByReceiveState()) {
+ return;
+ }
super.onReceiveStateChanged(sink, sourceId, state);
if (!mHysteresisModeFixAvailable || mStateByDevice == null
|| !mStateByDevice.containsKey(sink)) {
@@ -383,23 +521,21 @@ public class AudioStreamMediaService extends Service {
@Override
public void onDeviceVolumeChanged(
@NonNull BluetoothDevice device, @IntRange(from = -255, to = 255) int volume) {
- if (mStateByDevice == null || mStateByDevice.isEmpty()) {
- Log.w(TAG, "active device or device has source is null!");
+ if (!getDeviceInValidState().contains(device)) {
+ Log.w(TAG, "onDeviceVolumeChanged() : device not in valid state list");
return;
}
Log.d(
TAG,
"onDeviceVolumeChanged() bluetoothDevice : " + device + " volume: " + volume);
- if (mStateByDevice.containsKey(device)) {
- if (volume == 0) {
- mIsMuted = true;
- } else {
- mIsMuted = false;
- mLatestPositiveVolume = volume;
- }
- if (mLocalSession != null) {
- mLocalSession.setPlaybackState(getPlaybackState());
- }
+ if (volume == 0) {
+ mIsMuted = true;
+ } else {
+ mIsMuted = false;
+ mLatestPositiveVolume = volume;
+ }
+ if (mLocalSession != null) {
+ mLocalSession.setPlaybackState(getPlaybackState());
}
}
}
@@ -426,7 +562,7 @@ public class AudioStreamMediaService extends Service {
&& mStateByDevice != null) {
mStateByDevice.remove(cachedDevice.getDevice());
}
- if (mStateByDevice == null || mStateByDevice.isEmpty()) {
+ if (getDeviceInValidState().isEmpty()) {
Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf");
stopSelf();
}
@@ -484,11 +620,7 @@ public class AudioStreamMediaService extends Service {
}
private void handleOnPlay() {
- if (mStateByDevice == null || mStateByDevice.isEmpty()) {
- Log.w(TAG, "active device or device has source is null!");
- return;
- }
- mStateByDevice.keySet().forEach(device -> {
+ getDeviceInValidState().forEach(device -> {
Log.d(TAG, "onPlay() setting volume for device : " + device + " volume: "
+ mLatestPositiveVolume);
setDeviceVolume(device, mLatestPositiveVolume);
@@ -496,11 +628,7 @@ public class AudioStreamMediaService extends Service {
}
private void handleOnPause() {
- if (mStateByDevice == null || mStateByDevice.isEmpty()) {
- Log.w(TAG, "active device or device has source is null!");
- return;
- }
- mStateByDevice.keySet().forEach(device -> {
+ getDeviceInValidState().forEach(device -> {
Log.d(TAG, "onPause() setting volume for device : " + device + " volume: " + 0);
setDeviceVolume(device, /* volume= */ 0);
});
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedState.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedState.java
index 88393ab1b67..47a6da9510e 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedState.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedState.java
@@ -26,6 +26,7 @@ import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settingslib.flags.Flags;
class SourceAddedState extends AudioStreamStateHandler {
@VisibleForTesting
@@ -55,10 +56,12 @@ class SourceAddedState extends AudioStreamStateHandler {
if (cached != null) {
mAudioStreamsRepository.saveMetadata(context, cached);
}
- helper.startMediaService(
- context,
- preference.getAudioStreamBroadcastId(),
- String.valueOf(preference.getTitle()));
+ if (!Flags.audioStreamMediaServiceByReceiveState()) {
+ helper.startMediaService(
+ context,
+ preference.getAudioStreamBroadcastId(),
+ String.valueOf(preference.getTitle()));
+ }
mMetricsFeatureProvider.action(
preference.getContext(),
SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED,
diff --git a/src/com/android/settings/connecteddevice/display/ExternalDisplaySizePreference.java b/src/com/android/settings/connecteddevice/display/ExternalDisplaySizePreference.java
index df405ba4a94..bb3e1b37a18 100644
--- a/src/com/android/settings/connecteddevice/display/ExternalDisplaySizePreference.java
+++ b/src/com/android/settings/connecteddevice/display/ExternalDisplaySizePreference.java
@@ -81,9 +81,11 @@ public class ExternalDisplaySizePreference extends AccessibilitySeekBarPreferenc
implements SeekBar.OnSeekBarChangeListener {
private static final long MIN_COMMIT_INTERVAL_MS = 800;
private static final long CHANGE_BY_BUTTON_DELAY_MS = 300;
+ private static final long CHANGE_BY_SEEKBAR_DELAY_MS = 100;
private final DisplaySizeData mDisplaySizeData;
private int mLastDisplayProgress;
private long mLastCommitTime;
+ private boolean mSeekByTouch;
ExternalDisplaySizePreferenceStateHandler(DisplaySizeData displaySizeData) {
mDisplaySizeData = displaySizeData;
}
@@ -99,8 +101,7 @@ public class ExternalDisplaySizePreference extends AccessibilitySeekBarPreferenc
mLastCommitTime = SystemClock.elapsedRealtime();
}
- private void postCommitDelayed() {
- var commitDelayMs = CHANGE_BY_BUTTON_DELAY_MS;
+ private void postCommitDelayed(long commitDelayMs) {
if (SystemClock.elapsedRealtime() - mLastCommitTime < MIN_COMMIT_INTERVAL_MS) {
commitDelayMs += MIN_COMMIT_INTERVAL_MS;
}
@@ -112,13 +113,18 @@ public class ExternalDisplaySizePreference extends AccessibilitySeekBarPreferenc
@Override
public void onProgressChanged(@NonNull SeekBar seekBar, int i, boolean b) {
- postCommitDelayed();
+ if (!mSeekByTouch) postCommitDelayed(CHANGE_BY_BUTTON_DELAY_MS);
}
@Override
- public void onStartTrackingTouch(@NonNull SeekBar seekBar) {}
+ public void onStartTrackingTouch(@NonNull SeekBar seekBar) {
+ mSeekByTouch = true;
+ }
@Override
- public void onStopTrackingTouch(@NonNull SeekBar seekBar) {}
+ public void onStopTrackingTouch(@NonNull SeekBar seekBar) {
+ mSeekByTouch = false;
+ postCommitDelayed(CHANGE_BY_SEEKBAR_DELAY_MS);
+ }
}
}
diff --git a/src/com/android/settings/contract/SettingsContract.kt b/src/com/android/settings/contract/SettingsContract.kt
index da3867f35a4..3a354194eb6 100644
--- a/src/com/android/settings/contract/SettingsContract.kt
+++ b/src/com/android/settings/contract/SettingsContract.kt
@@ -104,3 +104,9 @@ const val KEY_RING_VOLUME = "separate_ring_volume"
/** Contract key for the "Remove animation" setting. */
const val KEY_REMOVE_ANIMATION = "remove_animation"
+
+/** Contract key for the "Pin media player. */
+const val KEY_PIN_MEDIA_PLAYER = "pin_media_player"
+
+/** Contract key for the "Show media on lock screen. */
+const val KEY_SHOW_MEDIA_ON_LOCK_SCREEN = "show_media_on_lock_screen"
diff --git a/src/com/android/settings/display/WidgetsOnLockscreenPreferenceController.java b/src/com/android/settings/display/WidgetsOnLockscreenPreferenceController.java
new file mode 100644
index 00000000000..32ba6ec5c23
--- /dev/null
+++ b/src/com/android/settings/display/WidgetsOnLockscreenPreferenceController.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2025 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.display;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+/** Controls the "widgets on lock screen" preferences (under "Display & touch"). */
+public class WidgetsOnLockscreenPreferenceController extends BasePreferenceController {
+ public WidgetsOnLockscreenPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return isAvailable(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ /**
+ * Returns whether "widgets on lock screen" preferences are available.
+ */
+ public static boolean isAvailable(Context context) {
+ if (!isMainUser(context)) {
+ return false;
+ }
+
+ return com.android.systemui.Flags.glanceableHubV2()
+ && (context.getResources().getBoolean(R.bool.config_show_communal_settings)
+ || context.getResources().getBoolean(
+ R.bool.config_show_communal_settings_mobile));
+ }
+
+ private static boolean isMainUser(Context context) {
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ return userManager.getUserInfo(UserHandle.myUserId()).isMain();
+ }
+}
diff --git a/src/com/android/settings/inputmethod/KeyboardAccessibilityKeysDialogFragment.java b/src/com/android/settings/inputmethod/KeyboardAccessibilityKeysDialogFragment.java
index 97d5b24b5ef..16a24204349 100644
--- a/src/com/android/settings/inputmethod/KeyboardAccessibilityKeysDialogFragment.java
+++ b/src/com/android/settings/inputmethod/KeyboardAccessibilityKeysDialogFragment.java
@@ -122,6 +122,8 @@ public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFrag
R.id.input_setting_keys_value_custom);
TextView customValueTextView = accessibilityKeyDialog.findViewById(
R.id.input_setting_keys_value_custom_value);
+ View seekbarView = accessibilityKeyDialog.findViewById(
+ R.id.input_setting_keys_custom_seekbar_layout);
SeekBar customProgressBar = accessibilityKeyDialog.findViewById(
R.id.input_setting_keys_value_custom_slider);
TextView titleTextView = accessibilityKeyDialog.findViewById(
@@ -147,7 +149,7 @@ public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFrag
customValueTextView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
customValueTextView.setText(
progressToThresholdInSecond(customProgressBar.getProgress()));
- customProgressBar.setVisibility(isChecked ? View.VISIBLE : View.GONE);
+ seekbarView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
buttonView.setChecked(isChecked);
});
cannedValueRadioGroup.setOnCheckedChangeListener(
@@ -174,14 +176,14 @@ public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFrag
// setting
initStateBasedOnThreshold(cannedValueRadioGroup, customRadioButton,
customValueTextView,
- customProgressBar);
+ customProgressBar, seekbarView);
} else if (customRadioButton.isChecked()) {
cannedValueRadioGroup.clearCheck();
customRadioButton.setChecked(true);
customValueTextView.setVisibility(View.VISIBLE);
customValueTextView.setText(
progressToThresholdInSecond(customProgressBar.getProgress()));
- customProgressBar.setVisibility(View.VISIBLE);
+ seekbarView.setVisibility(View.VISIBLE);
}
});
@@ -199,7 +201,7 @@ public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFrag
private void initStateBasedOnThreshold(RadioGroup cannedValueRadioGroup,
RadioButton customRadioButton, TextView customValueTextView,
- SeekBar customProgressBar) {
+ SeekBar customProgressBar, View seekbarView) {
int inputSettingKeysThreshold = getInputSettingKeysValue();
switch (inputSettingKeysThreshold) {
case 600 -> cannedValueRadioGroup.check(R.id.input_setting_keys_value_600);
@@ -213,5 +215,6 @@ public abstract class KeyboardAccessibilityKeysDialogFragment extends DialogFrag
customRadioButton.setChecked(true);
}
}
+ seekbarView.setVisibility(customRadioButton.isChecked() ? View.VISIBLE : View.GONE);
}
}
diff --git a/src/com/android/settings/network/ethernet/EthernetInterface.kt b/src/com/android/settings/network/ethernet/EthernetInterface.kt
index 1e2dc74752e..ddb138938a7 100644
--- a/src/com/android/settings/network/ethernet/EthernetInterface.kt
+++ b/src/com/android/settings/network/ethernet/EthernetInterface.kt
@@ -18,34 +18,68 @@ package com.android.settings.network.ethernet
import android.content.Context
import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
import android.net.EthernetManager
import android.net.EthernetManager.STATE_ABSENT
import android.net.EthernetNetworkManagementException
import android.net.EthernetNetworkUpdateRequest
import android.net.IpConfiguration
+import android.net.LinkProperties
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
import android.os.OutcomeReceiver
import android.util.Log
import androidx.core.content.ContextCompat
+import com.google.common.annotations.VisibleForTesting
class EthernetInterface(private val context: Context, private val id: String) :
EthernetManager.InterfaceStateListener {
+ interface EthernetInterfaceStateListener {
+ fun interfaceUpdated()
+ }
+
private val ethernetManager: EthernetManager? =
context.getSystemService(EthernetManager::class.java)
private val connectivityManager: ConnectivityManager? =
context.getSystemService(ConnectivityManager::class.java)
private val executor = ContextCompat.getMainExecutor(context)
+ private val interfaceListeners = mutableListOf()
private val TAG = "EthernetInterface"
+ private val networkRequest: NetworkRequest =
+ NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .build()
+
private var interfaceState = STATE_ABSENT
private var ipConfiguration = IpConfiguration()
+ private var linkProperties = LinkProperties()
fun getInterfaceState() = interfaceState
fun getId() = id
- fun getConfiguration(): IpConfiguration {
- return ipConfiguration
+ fun getConfiguration() = ipConfiguration
+
+ fun getLinkProperties() = linkProperties
+
+ fun registerListener(listener: EthernetInterfaceStateListener) {
+ if (interfaceListeners.isEmpty()) {
+ ethernetManager?.addInterfaceStateListener(ContextCompat.getMainExecutor(context), this)
+ connectivityManager?.registerNetworkCallback(networkRequest, networkCallback)
+ }
+ interfaceListeners.add(listener)
+ }
+
+ fun unregisterListener(listener: EthernetInterfaceStateListener) {
+ interfaceListeners.remove(listener)
+ if (interfaceListeners.isEmpty()) {
+ connectivityManager?.unregisterNetworkCallback(networkCallback)
+ ethernetManager?.removeInterfaceStateListener(this)
+ }
}
fun setConfiguration(ipConfiguration: IpConfiguration) {
@@ -67,10 +101,28 @@ class EthernetInterface(private val context: Context, private val id: String) :
)
}
+ private fun notifyListeners() {
+ for (listener in interfaceListeners) {
+ listener.interfaceUpdated()
+ }
+ }
+
override fun onInterfaceStateChanged(id: String, state: Int, role: Int, cfg: IpConfiguration?) {
if (id == this.id) {
ipConfiguration = cfg ?: IpConfiguration()
interfaceState = state
+ notifyListeners()
}
}
+
+ @VisibleForTesting
+ val networkCallback =
+ object : NetworkCallback() {
+ override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) {
+ if (lp.getInterfaceName().equals(id)) {
+ linkProperties = lp
+ notifyListeners()
+ }
+ }
+ }
}
diff --git a/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsController.kt b/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsController.kt
index cb3ad1db8ae..174fdbdf25d 100644
--- a/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsController.kt
+++ b/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsController.kt
@@ -18,7 +18,14 @@ package com.android.settings.network.ethernet
import android.content.Context
import android.net.EthernetManager
+import android.net.IpConfiguration
+import android.net.LinkProperties
+import android.net.StaticIpConfiguration
import android.widget.ImageView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import com.android.settings.R
@@ -30,13 +37,25 @@ class EthernetInterfaceDetailsController(
context: Context,
private val fragment: PreferenceFragmentCompat,
private val preferenceId: String,
-) : AbstractPreferenceController(context) {
+ private val lifecycle: Lifecycle,
+) :
+ AbstractPreferenceController(context),
+ EthernetInterface.EthernetInterfaceStateListener,
+ LifecycleEventObserver {
private val KEY_HEADER = "ethernet_details"
private val ethernetManager = context.getSystemService(EthernetManager::class.java)
private val ethernetInterface =
EthernetTrackerImpl.getInstance(context).getInterface(preferenceId)
+ private lateinit var entityHeaderController: EntityHeaderController
+
+ private var ipAddressPref: Preference? = null
+
+ init {
+ lifecycle.addObserver(this)
+ }
+
override fun isAvailable(): Boolean {
return true
}
@@ -45,10 +64,24 @@ class EthernetInterfaceDetailsController(
return KEY_HEADER
}
+ override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+ when (event) {
+ Lifecycle.Event.ON_START -> {
+ ethernetInterface?.registerListener(this)
+ }
+
+ Lifecycle.Event.ON_STOP -> {
+ ethernetInterface?.unregisterListener(this)
+ }
+
+ else -> {}
+ }
+ }
+
override fun displayPreference(screen: PreferenceScreen) {
val headerPref: LayoutPreference? = screen.findPreference(KEY_HEADER)
- val mEntityHeaderController =
+ entityHeaderController =
EntityHeaderController.newInstance(
fragment.getActivity(),
fragment,
@@ -59,17 +92,49 @@ class EthernetInterfaceDetailsController(
iconView?.setScaleType(ImageView.ScaleType.CENTER_INSIDE)
- mEntityHeaderController
- .setLabel("Ethernet")
- .setSummary(
- if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
- mContext.getString(R.string.network_connected)
- } else {
- mContext.getString(R.string.network_disconnected)
- }
- )
- .setSecondSummary("")
- .setIcon(mContext.getDrawable(R.drawable.ic_settings_ethernet))
- .done(true /* rebind */)
+ if (entityHeaderController != null) {
+ entityHeaderController
+ .setLabel("Ethernet")
+ .setSummary(
+ if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
+ mContext.getString(R.string.network_connected)
+ } else {
+ mContext.getString(R.string.network_disconnected)
+ }
+ )
+ .setSecondSummary("")
+ .setIcon(mContext.getDrawable(R.drawable.ic_settings_ethernet))
+ .done(true /* rebind */)
+ }
+
+ ipAddressPref = screen.findPreference("ethernet_ip_address")
+
+ if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
+ initializeIpDetails()
+ }
+ }
+
+ override fun interfaceUpdated() {
+ entityHeaderController?.setSummary(
+ if (ethernetInterface?.getInterfaceState() == EthernetManager.STATE_LINK_UP) {
+ mContext.getString(R.string.network_connected)
+ } else {
+ mContext.getString(R.string.network_disconnected)
+ }
+ )
+ initializeIpDetails()
+ }
+
+ private fun initializeIpDetails() {
+ val ipConfiguration: IpConfiguration? = ethernetInterface?.getConfiguration()
+ val linkProperties: LinkProperties? = ethernetInterface?.getLinkProperties()
+
+ if (ipConfiguration?.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
+ val staticIp: StaticIpConfiguration? = ipConfiguration?.getStaticIpConfiguration()
+ ipAddressPref?.setSummary(staticIp?.getIpAddress().toString())
+ } else {
+ val addresses = linkProperties?.getAddresses()
+ ipAddressPref?.setSummary(addresses?.first().toString())
+ }
}
}
diff --git a/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsFragment.kt b/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsFragment.kt
index a6cf90fed6b..46ed78f5aa5 100644
--- a/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsFragment.kt
+++ b/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsFragment.kt
@@ -50,6 +50,13 @@ class EthernetInterfaceDetailsFragment : DashboardFragment() {
override public fun createPreferenceControllers(
context: Context
): List {
- return listOf(EthernetInterfaceDetailsController(context, this, preferenceId ?: ""))
+ return listOf(
+ EthernetInterfaceDetailsController(
+ context,
+ this,
+ preferenceId ?: "",
+ getSettingsLifecycle(),
+ )
+ )
}
}
diff --git a/src/com/android/settings/safetycenter/OWNERS b/src/com/android/settings/safetycenter/OWNERS
new file mode 100644
index 00000000000..b89b09bbdbc
--- /dev/null
+++ b/src/com/android/settings/safetycenter/OWNERS
@@ -0,0 +1,4 @@
+jtomljanovic@google.com
+elliotsisteron@google.com
+deweytyl@google.com
+simonjw@google.com
diff --git a/src/com/android/settings/security/ActionDisabledByAdvancedProtectionDialog.kt b/src/com/android/settings/security/ActionDisabledByAdvancedProtectionDialog.kt
index f9ec7bcbcbe..cf29c3d707e 100644
--- a/src/com/android/settings/security/ActionDisabledByAdvancedProtectionDialog.kt
+++ b/src/com/android/settings/security/ActionDisabledByAdvancedProtectionDialog.kt
@@ -22,6 +22,7 @@ import android.security.advancedprotection.AdvancedProtectionManager.EXTRA_SUPPO
import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_CELLULAR_2G
import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES
import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_WEP
+import android.content.pm.PackageManager
import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_ENABLE_MTE
import android.security.advancedprotection.AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION
import android.security.advancedprotection.AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_DISABLED_SETTING
@@ -37,13 +38,17 @@ import com.android.settingslib.spa.SpaDialogWindowTypeActivity
import com.android.settingslib.spa.widget.dialog.AlertDialogButton
import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogContent
import com.android.settingslib.wifi.WifiUtils.Companion.DIALOG_WINDOW_TYPE
+import android.security.advancedprotection.AdvancedProtectionManager
class ActionDisabledByAdvancedProtectionDialog : SpaDialogWindowTypeActivity() {
@Composable
override fun Content() {
SettingsAlertDialogContent(
- confirmButton = AlertDialogButton(getString(R.string.okay)) { finish() },
+ confirmButton = AlertDialogButton(getString(R.string.okay)) {
+ finish()
+ logDialogShown(learnMoreClicked = false)
+ },
dismissButton = getSupportButtonIfExists(),
title = getString(R.string.disabled_by_advanced_protection_title),
icon = {
@@ -56,8 +61,8 @@ class ActionDisabledByAdvancedProtectionDialog : SpaDialogWindowTypeActivity() {
}
private fun getDialogMessage(): String {
- val featureId = intent.getIntExtra(EXTRA_SUPPORT_DIALOG_FEATURE, -1)
- val type = intent.getIntExtra(EXTRA_SUPPORT_DIALOG_TYPE, SUPPORT_DIALOG_TYPE_UNKNOWN)
+ val featureId = getIntentFeatureId()
+ val type = getIntentDialogueType()
val messageId = when (type) {
SUPPORT_DIALOG_TYPE_DISABLED_SETTING -> {
if (featureIdsWithSettingOn.contains(featureId)) {
@@ -93,6 +98,7 @@ class ActionDisabledByAdvancedProtectionDialog : SpaDialogWindowTypeActivity() {
) {
startActivity(helpIntent)
finish()
+ logDialogShown(learnMoreClicked = true)
}
} catch (e: Exception) {
Log.w(TAG, "Tried to set up help button, but this exception was thrown: ${e.message}")
@@ -100,10 +106,29 @@ class ActionDisabledByAdvancedProtectionDialog : SpaDialogWindowTypeActivity() {
return null
}
+ private fun logDialogShown(learnMoreClicked: Boolean) {
+ // We should always have this permission, but just in case we don't, we should not log.
+ if (checkSelfPermission(android.Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE)
+ != PackageManager.PERMISSION_GRANTED) {
+ return
+ }
+
+ this.getSystemService(AdvancedProtectionManager::class.java)
+ .logDialogShown(getIntentFeatureId(), getIntentDialogueType(), learnMoreClicked)
+ }
+
override fun getDialogWindowType(): Int? = if (intent.hasExtra(DIALOG_WINDOW_TYPE)) {
intent.getIntExtra(DIALOG_WINDOW_TYPE, WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW)
} else null
+ private fun getIntentFeatureId(): Int {
+ return intent.getIntExtra(EXTRA_SUPPORT_DIALOG_FEATURE, -1)
+ }
+
+ private fun getIntentDialogueType(): Int {
+ return intent.getIntExtra(EXTRA_SUPPORT_DIALOG_TYPE, SUPPORT_DIALOG_TYPE_UNKNOWN)
+ }
+
private companion object {
const val TAG = "AdvancedProtectionDlg"
val defaultMessageId = R.string.disabled_by_advanced_protection_action_message
diff --git a/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java b/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java
index b1685685bb4..e74db34dee0 100644
--- a/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java
+++ b/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java
@@ -178,7 +178,9 @@ public class ScreenLockPreferenceDetailsUtils {
if (!mLockPatternUtils.isSecure(userId)) {
if (userId == mProfileChallengeUserId
|| mLockPatternUtils.isLockScreenDisabled(userId)) {
- return R.string.unlock_set_unlock_mode_off;
+ return com.android.settings.flags.Flags.biometricsOnboardingEducation()
+ ? R.string.unlock_set_unlock_mode_off_new
+ : R.string.unlock_set_unlock_mode_off;
} else {
return R.string.unlock_set_unlock_mode_none;
}
diff --git a/src/com/android/settings/sound/MediaControlsLockscreenSwitchPreference.kt b/src/com/android/settings/sound/MediaControlsLockscreenSwitchPreference.kt
index 2d70b83ef31..7ad3a47f20e 100644
--- a/src/com/android/settings/sound/MediaControlsLockscreenSwitchPreference.kt
+++ b/src/com/android/settings/sound/MediaControlsLockscreenSwitchPreference.kt
@@ -16,9 +16,12 @@
package com.android.settings.sound
+import android.app.settings.SettingsEnums.ACTION_SHOW_MEDIA_ON_LOCK_SCREEN
import android.content.Context
import android.provider.Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN
import com.android.settings.R
+import com.android.settings.contract.KEY_SHOW_MEDIA_ON_LOCK_SCREEN
+import com.android.settings.metrics.PreferenceActionMetricsProvider
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyValueStoreDelegate
import com.android.settingslib.datastore.SettingsSecureStore
@@ -32,7 +35,13 @@ class MediaControlsLockscreenSwitchPreference :
KEY,
R.string.media_controls_lockscreen_title,
R.string.media_controls_lockscreen_description,
- ) {
+ ),
+ PreferenceActionMetricsProvider {
+
+ override val preferenceActionMetrics: Int
+ get() = ACTION_SHOW_MEDIA_ON_LOCK_SCREEN
+
+ override fun tags(context: Context) = arrayOf(KEY_SHOW_MEDIA_ON_LOCK_SCREEN)
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
diff --git a/src/com/android/settings/sound/MediaControlsSwitchPreference.kt b/src/com/android/settings/sound/MediaControlsSwitchPreference.kt
index 60df2658b7f..fd5354d4127 100644
--- a/src/com/android/settings/sound/MediaControlsSwitchPreference.kt
+++ b/src/com/android/settings/sound/MediaControlsSwitchPreference.kt
@@ -16,30 +16,39 @@
package com.android.settings.sound
+import android.app.settings.SettingsEnums.ACTION_PIN_MEDIA_PLAYER
import android.content.Context
import android.provider.Settings.Secure.MEDIA_CONTROLS_RESUME
-
+import com.android.settings.R
+import com.android.settings.contract.KEY_PIN_MEDIA_PLAYER
+import com.android.settings.metrics.PreferenceActionMetricsProvider
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.SettingsSecureStore
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.SwitchPreference
-import com.android.settings.R
// LINT.IfChange
class MediaControlsSwitchPreference(
- private val mediaControlsStore: MediaControlsScreen.MediaControlsStore,
-) : SwitchPreference(
- KEY,
- R.string.media_controls_resume_title,
- R.string.media_controls_resume_description,
-) {
+ private val mediaControlsStore: MediaControlsScreen.MediaControlsStore
+) :
+ SwitchPreference(
+ KEY,
+ R.string.media_controls_resume_title,
+ R.string.media_controls_resume_description,
+ ),
+ PreferenceActionMetricsProvider {
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
override val keywords: Int
get() = R.string.keywords_media_controls
+ override val preferenceActionMetrics: Int
+ get() = ACTION_PIN_MEDIA_PLAYER
+
+ override fun tags(context: Context) = arrayOf(KEY_PIN_MEDIA_PLAYER)
+
override fun getReadPermissions(context: Context) = SettingsSecureStore.getReadPermissions()
override fun getWritePermissions(context: Context) = SettingsSecureStore.getWritePermissions()
@@ -56,4 +65,4 @@ class MediaControlsSwitchPreference(
const val KEY = MEDIA_CONTROLS_RESUME
}
}
-// LINT.ThenChange(MediaControlsPreferenceController.java)
\ No newline at end of file
+// LINT.ThenChange(MediaControlsPreferenceController.java)
diff --git a/src/com/android/settings/spa/app/appcompat/OWNERS b/src/com/android/settings/spa/app/appcompat/OWNERS
new file mode 100644
index 00000000000..5ef6ded95f1
--- /dev/null
+++ b/src/com/android/settings/spa/app/appcompat/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 970984
+# Large Screen Experiences App Compat
+gracielawputri@google.com
+mcarli@google.com
+mariiasand@google.com
\ No newline at end of file
diff --git a/src/com/android/settings/supervision/ConfirmSupervisionCredentialsActivity.kt b/src/com/android/settings/supervision/ConfirmSupervisionCredentialsActivity.kt
new file mode 100644
index 00000000000..b459f5383b5
--- /dev/null
+++ b/src/com/android/settings/supervision/ConfirmSupervisionCredentialsActivity.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2025 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.supervision
+
+import android.Manifest.permission.USE_BIOMETRIC
+import android.app.Activity
+import android.content.pm.PackageManager
+import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback
+import android.os.Bundle
+import android.os.CancellationSignal
+import android.util.Log
+import androidx.annotation.RequiresPermission
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.FragmentActivity
+import com.android.settings.R
+
+/**
+ * Activity for confirming supervision credentials using device credential authentication.
+ *
+ * This activity displays an authentication prompt to the user, requiring them to authenticate using
+ * their device credentials (PIN, pattern, or password). It is specifically designed for verifying
+ * credentials for supervision purposes.
+ *
+ * It returns `Activity.RESULT_OK` if authentication succeeds, and `Activity.RESULT_CANCELED` if
+ * authentication fails or is canceled by the user.
+ *
+ * Usage:
+ * 1. Start this activity using `startActivityForResult()`.
+ * 2. Handle the result in `onActivityResult()`.
+ *
+ * Permissions:
+ * - Requires `android.permission.USE_BIOMETRIC`.
+ */
+class ConfirmSupervisionCredentialsActivity : FragmentActivity() {
+ private val mAuthenticationCallback =
+ object : AuthenticationCallback() {
+ override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
+ Log.w(TAG, "onAuthenticationError(errorCode=$errorCode, errString=$errString)")
+ setResult(Activity.RESULT_CANCELED)
+ finish()
+ }
+
+ override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) {
+ setResult(Activity.RESULT_OK)
+ finish()
+ }
+
+ override fun onAuthenticationFailed() {
+ setResult(Activity.RESULT_CANCELED)
+ finish()
+ }
+ }
+
+ @RequiresPermission(USE_BIOMETRIC)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ // TODO(b/392961554): Check if caller is the SYSTEM_SUPERVISION role holder. Call
+ // RoleManager#getRoleHolders(SYSTEM_SUPERVISION) and check if getCallingPackage() is in the
+ // list.
+ if (checkCallingOrSelfPermission(USE_BIOMETRIC) == PackageManager.PERMISSION_GRANTED) {
+ showBiometricPrompt()
+ }
+ }
+
+ @RequiresPermission(USE_BIOMETRIC)
+ fun showBiometricPrompt() {
+ // TODO(b/392961554): adapts to new user profile type to trigger PIN verification dialog.
+ val biometricPrompt =
+ BiometricPrompt.Builder(this)
+ .setTitle(getString(R.string.supervision_full_screen_pin_verification_title))
+ .setConfirmationRequired(true)
+ .setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ .build()
+ biometricPrompt.authenticate(
+ CancellationSignal(),
+ ContextCompat.getMainExecutor(this),
+ mAuthenticationCallback,
+ )
+ }
+
+ companion object {
+ // TODO(b/392961554): remove this tag and use shared tag after http://ag/31997167 is
+ // submitted.
+ const val TAG = "SupervisionSettings"
+ }
+}
diff --git a/src/com/android/settings/supervision/SupervisionDashboardScreen.kt b/src/com/android/settings/supervision/SupervisionDashboardScreen.kt
index 86f77f726b6..674c0f3a7fb 100644
--- a/src/com/android/settings/supervision/SupervisionDashboardScreen.kt
+++ b/src/com/android/settings/supervision/SupervisionDashboardScreen.kt
@@ -56,7 +56,7 @@ class SupervisionDashboardScreen : PreferenceScreenCreator {
override fun getPreferenceHierarchy(context: Context) =
preferenceHierarchy(context, this) {
- +SupervisionMainSwitchPreference()
+ +SupervisionMainSwitchPreference(context)
+TitlelessPreferenceGroup(SUPERVISION_DYNAMIC_GROUP_1) += {
+SupervisionWebContentFiltersScreen.KEY
}
diff --git a/src/com/android/settings/supervision/SupervisionMainSwitchPreference.kt b/src/com/android/settings/supervision/SupervisionMainSwitchPreference.kt
index 5a84137ecb6..88afc55147c 100644
--- a/src/com/android/settings/supervision/SupervisionMainSwitchPreference.kt
+++ b/src/com/android/settings/supervision/SupervisionMainSwitchPreference.kt
@@ -15,8 +15,10 @@
*/
package com.android.settings.supervision
+import android.app.Activity
import android.app.supervision.SupervisionManager
import android.content.Context
+import android.content.Intent
import androidx.preference.Preference
import com.android.settings.R
import com.android.settingslib.datastore.KeyValueStore
@@ -32,19 +34,22 @@ import com.android.settingslib.preference.MainSwitchPreferenceBinding
import com.android.settingslib.preference.forEachRecursively
/** Main toggle to enable or disable device supervision. */
-class SupervisionMainSwitchPreference :
+class SupervisionMainSwitchPreference(context: Context) :
MainSwitchPreference(KEY, R.string.device_supervision_switch_title),
PreferenceSummaryProvider,
MainSwitchPreferenceBinding,
Preference.OnPreferenceChangeListener,
PreferenceLifecycleProvider {
+ private val supervisionMainSwitchStorage = SupervisionMainSwitchStorage(context)
+ private lateinit var lifeCycleContext: PreferenceLifecycleContext
+
// TODO(b/383568136): Make presence of summary conditional on whether PIN
// has been set up before or not.
override fun getSummary(context: Context): CharSequence? =
context.getString(R.string.device_supervision_switch_no_pin_summary)
- override fun storage(context: Context): KeyValueStore = SupervisionMainSwitchStorage(context)
+ override fun storage(context: Context): KeyValueStore = supervisionMainSwitchStorage
override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.DISALLOW
@@ -55,26 +60,49 @@ class SupervisionMainSwitchPreference :
override val sensitivityLevel: Int
get() = SensitivityLevel.HIGH_SENSITIVITY
+ override fun onCreate(context: PreferenceLifecycleContext) {
+ lifeCycleContext = context
+ }
+
+ override fun onResume(context: PreferenceLifecycleContext) {
+ updateDependentPreferencesEnabledState(
+ context.findPreference(KEY),
+ supervisionMainSwitchStorage.getBoolean(KEY)!!,
+ )
+ }
+
+ override fun onActivityResult(
+ context: PreferenceLifecycleContext,
+ requestCode: Int,
+ resultCode: Int,
+ data: Intent?,
+ ): Boolean {
+ if (resultCode == Activity.RESULT_OK) {
+ val mainSwitchPreference =
+ context.requirePreference(KEY)
+ val newValue = !supervisionMainSwitchStorage.getBoolean(KEY)!!
+ mainSwitchPreference.setChecked(newValue)
+ updateDependentPreferencesEnabledState(mainSwitchPreference, newValue)
+ }
+
+ return true
+ }
+
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata)
preference.onPreferenceChangeListener = this
}
- override fun onResume(context: PreferenceLifecycleContext) {
- val currentValue = storage(context.applicationContext)?.getBoolean(key) ?: false
-
- updateDependentPreferencesEnabledState(
- context.findPreference(KEY),
- currentValue,
- )
- }
-
override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
if (newValue !is Boolean) return true
- updateDependentPreferencesEnabledState(preference, newValue)
-
- return true
+ val intent = Intent(lifeCycleContext, ConfirmSupervisionCredentialsActivity::class.java)
+ lifeCycleContext.startActivityForResult(
+ intent,
+ REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
+ null,
+ )
+ return false
}
private fun updateDependentPreferencesEnabledState(
@@ -83,9 +111,8 @@ class SupervisionMainSwitchPreference :
) {
preference?.parent?.forEachRecursively {
if (
- it.parent?.key?.toString() ==
- SupervisionDashboardScreen.SUPERVISION_DYNAMIC_GROUP_1 ||
- it.key?.toString() == SupervisionPinManagementScreen.KEY
+ it.parent?.key == SupervisionDashboardScreen.SUPERVISION_DYNAMIC_GROUP_1 ||
+ it.key == SupervisionPinManagementScreen.KEY
) {
it.isEnabled = isChecked
}
@@ -103,7 +130,6 @@ class SupervisionMainSwitchPreference :
as T
override fun setValue(key: String, valueType: Class, value: T?) {
- // TODO(b/392694561): add PIN protection to main toggle.
if (key == KEY && value is Boolean) {
val supervisionManager = context.getSystemService(SupervisionManager::class.java)
supervisionManager?.setSupervisionEnabled(value)
@@ -113,5 +139,6 @@ class SupervisionMainSwitchPreference :
companion object {
const val KEY = "device_supervision_switch"
+ const val REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS = 0
}
}
diff --git a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java
index 375952f725c..81c869d5063 100644
--- a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java
@@ -16,6 +16,8 @@
package com.android.settings.accessibility;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -76,6 +78,15 @@ public class TextReadingPreviewControllerTest {
mDisplaySizePreference = new AccessibilitySeekBarPreference(mContext, /* attr= */ null);
}
+ @Test
+ public void numberOfPreviewSamples_numberOfPreviewContentDescription_isEqual() {
+ int[] previewSamples = TextReadingPreviewController.getPreviewSampleLayouts(mContext);
+ int[] previewContentDescriptions =
+ TextReadingPreviewController.getPreviewSampleContentDescriptions(mContext);
+
+ assertThat(previewSamples.length).isEqualTo(previewContentDescriptions.length);
+ }
+
@Test
public void initPreviewerAdapter_verifyAction() {
when(mPreferenceScreen.findPreference(PREVIEW_KEY)).thenReturn(mPreviewPreference);
diff --git a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java
index 4ca1dca3de5..9cd8fa232c5 100644
--- a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java
@@ -49,29 +49,48 @@ import org.robolectric.RobolectricTestRunner;
*/
@RunWith(RobolectricTestRunner.class)
public class TextReadingPreviewPreferenceTest {
-
+ private Context mContext;
private TextReadingPreviewPreference mTextReadingPreviewPreference;
private PreferenceViewHolder mHolder;
private ViewPager mViewPager;
private PreviewPagerAdapter mPreviewPagerAdapter;
private int mPreviewSampleCount;
+ private int[] mPreviewContentDescriptions;
@Before
public void setUp() {
- final Context context = ApplicationProvider.getApplicationContext();
- final int[] previewSamples = TextReadingPreviewController.getPreviewSampleLayouts(context);
+ mContext = ApplicationProvider.getApplicationContext();
+ mPreviewContentDescriptions =
+ TextReadingPreviewController.getPreviewSampleContentDescriptions(mContext);
+ final int[] previewSamples = TextReadingPreviewController.getPreviewSampleLayouts(mContext);
mPreviewSampleCount = previewSamples.length;
final Configuration[] configurations = createConfigurations(mPreviewSampleCount);
- mTextReadingPreviewPreference = new TextReadingPreviewPreference(context);
+ mTextReadingPreviewPreference = new TextReadingPreviewPreference(mContext);
mPreviewPagerAdapter =
- spy(new PreviewPagerAdapter(context, /* isLayoutRtl= */ false,
+ spy(new PreviewPagerAdapter(mContext, /* isLayoutRtl= */ false,
previewSamples, configurations));
- final LayoutInflater inflater = LayoutInflater.from(context);
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
final View view =
inflater.inflate(mTextReadingPreviewPreference.getLayoutResource(),
- new LinearLayout(context), false);
+ new LinearLayout(mContext), false);
mHolder = PreferenceViewHolder.createInstanceForTests(view);
mViewPager = view.findViewById(R.id.preview_pager);
+ mTextReadingPreviewPreference.setContentDescription(mPreviewContentDescriptions);
+ }
+
+ @Test
+ public void changePreviewPage_getExpectedContentDescription() {
+ mTextReadingPreviewPreference.setPreviewAdapter(mPreviewPagerAdapter);
+ mTextReadingPreviewPreference.onBindViewHolder(mHolder);
+
+ // Verify the initial content description
+ assertThat(mViewPager.getContentDescription().toString())
+ .isEqualTo(mContext.getString(mPreviewContentDescriptions[0]));
+
+ // Change the preview page
+ mViewPager.setCurrentItem(1);
+ assertThat(mViewPager.getContentDescription().toString())
+ .isEqualTo(mContext.getString(mPreviewContentDescriptions[1]));
}
@Test
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickMainSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickMainSwitchPreferenceControllerTest.java
new file mode 100644
index 00000000000..b2f648acb70
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickMainSwitchPreferenceControllerTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
+import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.core.BasePreferenceController;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ToggleAutoclickMainSwitchPreferenceControllerTest {
+
+ private static final String PREFERENCE_KEY = "accessibility_autoclick_main_switch";
+
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private ToggleAutoclickMainSwitchPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ mController = new ToggleAutoclickMainSwitchPreferenceController(mContext, PREFERENCE_KEY);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void getAvailabilityStatus_availableWhenFlagOn() {
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.AVAILABLE);
+ }
+
+ @Test
+ public void setChecked_withTrue_shouldUpdateSetting() {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF);
+
+ mController.setChecked(true);
+
+ assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF))
+ .isEqualTo(ON);
+ }
+
+ @Test
+ public void setChecked_withFalse_shouldUpdateSetting() {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, ON);
+
+ mController.setChecked(false);
+
+ assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF))
+ .isEqualTo(OFF);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceStatusPreferenceControllerTest.java
index c426f9479ef..8f565147247 100644
--- a/tests/robotests/src/com/android/settings/biometrics/face/FaceStatusPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceStatusPreferenceControllerTest.java
@@ -36,6 +36,9 @@ import android.content.pm.PackageManager;
import android.hardware.face.Face;
import android.hardware.face.FaceManager;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
@@ -50,6 +53,7 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -67,6 +71,8 @@ public class FaceStatusPreferenceControllerTest {
private static final String TEST_PREF_KEY = "baz";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private LockPatternUtils mLockPatternUtils;
@Mock
@@ -125,7 +131,8 @@ public class FaceStatusPreferenceControllerTest {
}
@Test
- public void updateState_noFace_shouldShowDefaultSummary() {
+ @DisableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void updateState_noFace_flagOff_shouldShowDefaultSummary() {
when(mFaceManager.isHardwareDetected()).thenReturn(true);
mController.updateState(mPreference);
@@ -135,6 +142,18 @@ public class FaceStatusPreferenceControllerTest {
assertThat(mPreference.isVisible()).isTrue();
}
+ @Test
+ @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void updateState_noFace_flagOn_shouldShowDefaultSummary() {
+ when(mFaceManager.isHardwareDetected()).thenReturn(true);
+
+ mController.updateState(mPreference);
+
+ assertThat(mPreference.getSummary()).isEqualTo(
+ mContext.getString(R.string.security_settings_face_preference_summary_none_new));
+ assertThat(mPreference.isVisible()).isTrue();
+ }
+
@Test
public void updateState_hasFace_shouldShowSummary() {
when(mFaceManager.isHardwareDetected()).thenReturn(true);
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceControllerTest.java
index 000ee933dc4..de8c0709884 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceControllerTest.java
@@ -36,6 +36,9 @@ import android.content.pm.PackageManager;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
@@ -51,6 +54,7 @@ import com.android.settingslib.utils.StringUtil;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -66,6 +70,8 @@ import java.util.Collections;
@Config(shadows = {ShadowRestrictedLockUtilsInternal.class})
public class FingerprintStatusPreferenceControllerTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private LockPatternUtils mLockPatternUtils;
@Mock
@@ -125,7 +131,8 @@ public class FingerprintStatusPreferenceControllerTest {
}
@Test
- public void updateState_noFingerprint_shouldShowDefaultSummary() {
+ @DisableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void updateState_noFingerprint_flagOff_shouldShowDefaultSummary() {
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
mController.updateState(mPreference);
@@ -135,6 +142,19 @@ public class FingerprintStatusPreferenceControllerTest {
assertThat(mPreference.isVisible()).isTrue();
}
+ @Test
+ @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void updateState_noFingerprint_flagOn_shouldShowDefaultSummary() {
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+
+ mController.updateState(mPreference);
+
+ assertThat(mPreference.getSummary()).isEqualTo(
+ mContext.getString(
+ R.string.security_settings_fingerprint_preference_summary_none_new));
+ assertThat(mPreference.isVisible()).isTrue();
+ }
+
@Test
public void updateState_hasFingerprint_shouldShowSummary() {
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
diff --git a/tests/robotests/src/com/android/settings/communal/CommunalPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/communal/CommunalPreferenceControllerTest.java
index cc970eb6b1d..17a51794ebc 100644
--- a/tests/robotests/src/com/android/settings/communal/CommunalPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/communal/CommunalPreferenceControllerTest.java
@@ -16,11 +16,8 @@
package com.android.settings.communal;
-import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -66,6 +63,7 @@ public class CommunalPreferenceControllerTest {
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_communalEnabled_shouldBeTrueForPrimaryUser() {
setCommunalEnabled(true);
mShadowUserManager.setUserForeground(true);
@@ -73,6 +71,7 @@ public class CommunalPreferenceControllerTest {
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_communalEnabled_shouldBeFalseForSecondaryUser() {
setCommunalEnabled(true);
mShadowUserManager.setUserForeground(false);
@@ -80,6 +79,7 @@ public class CommunalPreferenceControllerTest {
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
public void isAvailable_communalDisabled_shouldBeFalseForPrimaryUser() {
setCommunalEnabled(false);
mShadowUserManager.setUserForeground(true);
@@ -88,36 +88,8 @@ public class CommunalPreferenceControllerTest {
@Test
@EnableFlags(FLAG_GLANCEABLE_HUB_V2)
- public void isAvailable_communalOnMobileEnabled_shouldBeTrueForPrimaryUser() {
- setCommunalEnabled(false);
- setCommunalOnMobileEnabled(true);
- mShadowUserManager.setUserForeground(true);
- assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
- }
-
- @Test
- @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
- public void isAvailable_communalOnMobileEnabled_shouldBeFalseForSecondaryUser() {
- setCommunalEnabled(false);
- setCommunalOnMobileEnabled(true);
- mShadowUserManager.setUserForeground(false);
- assertFalse(mController.isAvailable());
- }
-
- @Test
- @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
- public void isAvailable_communalOnMobileDisabled_shouldBeFalseForPrimaryUser() {
- setCommunalEnabled(false);
- setCommunalOnMobileEnabled(false);
- mShadowUserManager.setUserForeground(true);
- assertFalse(mController.isAvailable());
- }
-
- @Test
- @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
- public void isAvailable_glanceableHubV2FlagDisabled_shouldBeFalseForPrimaryUser() {
- setCommunalEnabled(false);
- setCommunalOnMobileEnabled(true);
+ public void isAvailable_glanceableHubV2Enabled_shouldBeFalseForPrimaryUser() {
+ setCommunalEnabled(true);
mShadowUserManager.setUserForeground(true);
assertFalse(mController.isAvailable());
}
@@ -125,9 +97,4 @@ public class CommunalPreferenceControllerTest {
private void setCommunalEnabled(boolean enabled) {
SettingsShadowResources.overrideResource(R.bool.config_show_communal_settings, enabled);
}
-
- private void setCommunalOnMobileEnabled(boolean enabled) {
- SettingsShadowResources.overrideResource(
- R.bool.config_show_communal_settings_mobile, enabled);
- }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceControllerTest.java
index 29f427449e7..a2484127b90 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceControllerTest.java
@@ -45,6 +45,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -79,6 +80,7 @@ import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothEventManager;
+import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HeadsetProfile;
@@ -144,6 +146,7 @@ public class AudioSharingDevicePreferenceControllerTest {
@Mock private LeAudioProfile mLeAudioProfile;
@Mock private A2dpProfile mA2dpProfile;
@Mock private HeadsetProfile mHeadsetProfile;
+ @Mock private ContentResolver mContentResolver;
private Context mContext;
private AudioSharingDevicePreferenceController mController;
@@ -156,7 +159,7 @@ public class AudioSharingDevicePreferenceControllerTest {
@Before
public void setUp() {
- mContext = ApplicationProvider.getApplicationContext();
+ mContext = spy(ApplicationProvider.getApplicationContext());
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
@@ -185,6 +188,7 @@ public class AudioSharingDevicePreferenceControllerTest {
when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP);
when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO);
when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true);
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mScreen.getContext()).thenReturn(mContext);
mPreferenceGroup = spy(new PreferenceCategory(mContext));
doReturn(mPreferenceManager).when(mPreferenceGroup).getPreferenceManager();
@@ -211,6 +215,12 @@ public class AudioSharingDevicePreferenceControllerTest {
verify(mAssistant, never())
.registerServiceCallBack(
any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
+ verify(mContentResolver, never())
+ .registerContentObserver(
+ Settings.Secure.getUriFor(
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast()),
+ false,
+ mController.mSettingsObserver);
verify(mBluetoothDeviceUpdater, never()).registerCallback();
verify(mBluetoothDeviceUpdater, never()).refreshPreference();
}
@@ -224,6 +234,12 @@ public class AudioSharingDevicePreferenceControllerTest {
verify(mAssistant)
.registerServiceCallBack(
any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
+ verify(mContentResolver)
+ .registerContentObserver(
+ Settings.Secure.getUriFor(
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast()),
+ false,
+ mController.mSettingsObserver);
verify(mBluetoothDeviceUpdater).registerCallback();
verify(mBluetoothDeviceUpdater).refreshPreference();
}
@@ -236,6 +252,7 @@ public class AudioSharingDevicePreferenceControllerTest {
verify(mDialogHandler, never()).unregisterCallbacks();
verify(mAssistant, never())
.unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
+ verify(mContentResolver, never()).unregisterContentObserver(mController.mSettingsObserver);
verify(mBluetoothDeviceUpdater, never()).unregisterCallback();
}
@@ -247,6 +264,7 @@ public class AudioSharingDevicePreferenceControllerTest {
verify(mDialogHandler).unregisterCallbacks();
verify(mAssistant)
.unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
+ verify(mContentResolver).unregisterContentObserver(mController.mSettingsObserver);
verify(mBluetoothDeviceUpdater).unregisterCallback();
}
@@ -485,6 +503,12 @@ public class AudioSharingDevicePreferenceControllerTest {
verifyNoInteractions(mDialogHandler);
}
+ @Test
+ public void onFallbackDeviceChanged_updateSummary() {
+ mController.mSettingsObserver.onChange(true);
+ verify(mBluetoothDeviceUpdater).refreshPreference();
+ }
+
@Test
@EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
public void handleDeviceClickFromIntent_noDevice_doNothing() {
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java
index 056514620d2..820f13028f4 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java
@@ -26,6 +26,9 @@ import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -39,6 +42,7 @@ import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.lifecycle.LifecycleOwner;
@@ -52,6 +56,7 @@ import com.android.settings.testutils.shadow.ShadowThreadUtils;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.widget.LayoutPreference;
import org.junit.After;
@@ -254,6 +259,7 @@ public class AudioStreamHeaderControllerTest {
}
@Test
+ @EnableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE)
public void testCallback_onReceiveStateChanged_updateButton() {
when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(Map.of(BROADCAST_ID, STREAMING));
@@ -271,6 +277,7 @@ public class AudioStreamHeaderControllerTest {
verify(mHeaderController, times(2))
.setSummary(mContext.getString(AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY));
verify(mHeaderController, times(2)).done(true);
+ verify(mAudioStreamsHelper, never()).startMediaService(any(), anyInt(), anyString());
}
@Test
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java
index c82c9787ccd..c00e2a5e379 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java
@@ -19,6 +19,10 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_ID;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.DEVICES;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.LEAVE_BROADCAST_ACTION;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.google.common.truth.Truth.assertThat;
@@ -57,6 +61,7 @@ import android.os.Looper;
import android.os.RemoteException;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.DisplayMetrics;
+import android.view.KeyEvent;
import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper;
import com.android.settings.testutils.FakeFeatureFactory;
@@ -69,6 +74,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.bluetooth.PrivateBroadcastReceiveData;
import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.flags.Flags;
@@ -116,6 +122,8 @@ public class AudioStreamMediaServiceTest {
@Mock private VolumeControlProfile mVolumeControlProfile;
@Mock private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock private BluetoothDevice mDevice;
+ @Mock
+ private BluetoothDevice mDevice2;
@Mock private ISession mISession;
@Mock private ISessionController mISessionController;
@Mock private PackageManager mPackageManager;
@@ -240,6 +248,7 @@ public class AudioStreamMediaServiceTest {
@Test
public void onDestroy_flagOn_cleanup() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
var devices = new ArrayList();
devices.add(mDevice);
@@ -256,8 +265,33 @@ public class AudioStreamMediaServiceTest {
verify(mVolumeControlProfile).unregisterCallback(any());
}
+ @Test
+ public void byReceiveStateFlagOn_onDestroy_flagOn_cleanup() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
+
+ Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
+ mAudioStreamMediaService.onCreate();
+ mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
+ mAudioStreamMediaService.onDestroy();
+
+ verify(mBluetoothEventManager).unregisterCallback(any());
+ verify(mLeBroadcastAssistant).unregisterServiceCallBack(any());
+ verify(mVolumeControlProfile).unregisterCallback(any());
+ }
+
+ @Test
+ public void byReceiveStateFlagOn_onStartCommand_invalidData_stopSelf() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
+ Intent intent = setupReceiveDataIntent(-1, mDevice, STREAMING);
+ mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
+
+ verify(mAudioStreamMediaService).stopSelf();
+ }
+
@Test
public void onStartCommand_noBroadcastId_stopSelf() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onStartCommand(new Intent(), /* flags= */ 0, /* startId= */ 0);
verify(mAudioStreamMediaService).stopSelf();
@@ -265,6 +299,7 @@ public class AudioStreamMediaServiceTest {
@Test
public void onStartCommand_noDevice_stopSelf() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
Intent intent = new Intent();
intent.putExtra(BROADCAST_ID, 1);
@@ -273,8 +308,110 @@ public class AudioStreamMediaServiceTest {
verify(mAudioStreamMediaService).stopSelf();
}
+ @Test
+ public void byReceiveStateFlagOn_onStartCommand_createSessionAndStartForeground() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
+ Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
+ mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
+
+ ArgumentCaptor notificationCapture = ArgumentCaptor.forClass(
+ Notification.class);
+ verify(mAudioStreamMediaService).startForeground(anyInt(), notificationCapture.capture());
+ var notification = notificationCapture.getValue();
+ assertThat(notification.getSmallIcon()).isNotNull();
+ assertThat(notification.isStyle(Notification.MediaStyle.class)).isTrue();
+
+ verify(mAudioStreamMediaService, never()).stopSelf();
+ }
+
+ @Test
+ public void byReceiveStateFlagOn_onStartCommand_decryptionFailed_stopSelf() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
+ Intent intent = setupReceiveDataIntent(1, mDevice, DECRYPTION_FAILED);
+ mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
+
+ verify(mAudioStreamMediaService).stopSelf();
+ }
+
+ @Test
+ public void byReceiveStateFlagOn_onStartCommand_addDevice() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
+ mAudioStreamMediaService.onCreate();
+ Intent intent1 = setupReceiveDataIntent(1, mDevice, STREAMING);
+ mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0);
+ Intent intent2 = setupReceiveDataIntent(1, mDevice2, PAUSED);
+ mAudioStreamMediaService.onStartCommand(intent2, /* flags= */ 0, /* startId= */ 0);
+
+ ArgumentCaptor notificationCapture = ArgumentCaptor.forClass(
+ Notification.class);
+ verify(mAudioStreamMediaService).startForeground(anyInt(), notificationCapture.capture());
+ var notification = notificationCapture.getValue();
+ assertThat(notification.getSmallIcon()).isNotNull();
+ assertThat(notification.isStyle(Notification.MediaStyle.class)).isTrue();
+
+ assertThat(mAudioStreamMediaService.mStateByDevice).isNotNull();
+ var deviceState = mAudioStreamMediaService.mStateByDevice.get(mDevice);
+ assertThat(deviceState).isNotNull();
+ assertThat(deviceState).isEqualTo(STREAMING);
+ var device2State = mAudioStreamMediaService.mStateByDevice.get(mDevice2);
+ assertThat(device2State).isNotNull();
+ assertThat(device2State).isEqualTo(PAUSED);
+ verify(mAudioStreamMediaService, never()).stopSelf();
+ verify(mNotificationManager).notify(anyInt(), any());
+ }
+
+ @Test
+ public void byReceiveStateFlagOn_onStartCommand_updateState() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
+ mAudioStreamMediaService.onCreate();
+ Intent intent1 = setupReceiveDataIntent(1, mDevice, STREAMING);
+ mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0);
+ Intent intent2 = setupReceiveDataIntent(1, mDevice, PAUSED);
+ mAudioStreamMediaService.onStartCommand(intent2, /* flags= */ 0, /* startId= */ 0);
+
+ ArgumentCaptor notificationCapture = ArgumentCaptor.forClass(
+ Notification.class);
+ verify(mAudioStreamMediaService).startForeground(anyInt(), notificationCapture.capture());
+ var notification = notificationCapture.getValue();
+ assertThat(notification.getSmallIcon()).isNotNull();
+ assertThat(notification.isStyle(Notification.MediaStyle.class)).isTrue();
+
+ assertThat(mAudioStreamMediaService.mStateByDevice).isNotNull();
+ var deviceState = mAudioStreamMediaService.mStateByDevice.get(mDevice);
+ assertThat(deviceState).isNotNull();
+ assertThat(deviceState).isEqualTo(PAUSED);
+ verify(mAudioStreamMediaService, never()).stopSelf();
+ verify(mNotificationManager).notify(anyInt(), any());
+ }
+
+ @Test
+ public void byReceiveStateFlagOn_onStartCommand_newBroadcastId() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
+ mAudioStreamMediaService.onCreate();
+ Intent intent1 = setupReceiveDataIntent(1, mDevice, STREAMING);
+ mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0);
+ Intent intent2 = setupReceiveDataIntent(2, mDevice2, PAUSED);
+ mAudioStreamMediaService.onStartCommand(intent2, /* flags= */ 0, /* startId= */ 0);
+
+ ArgumentCaptor notificationCapture = ArgumentCaptor.forClass(
+ Notification.class);
+ verify(mAudioStreamMediaService).startForeground(anyInt(), notificationCapture.capture());
+ var notification = notificationCapture.getValue();
+ assertThat(notification.getSmallIcon()).isNotNull();
+ assertThat(notification.isStyle(Notification.MediaStyle.class)).isTrue();
+
+ assertThat(mAudioStreamMediaService.mStateByDevice).isNotNull();
+ var oldDeviceState = mAudioStreamMediaService.mStateByDevice.get(mDevice);
+ assertThat(oldDeviceState).isNull();
+ var newDeviceState = mAudioStreamMediaService.mStateByDevice.get(mDevice2);
+ assertThat(newDeviceState).isEqualTo(PAUSED);
+ verify(mAudioStreamMediaService, never()).stopSelf();
+ verify(mNotificationManager).notify(anyInt(), any());
+ }
+
@Test
public void onStartCommand_createSessionAndStartForeground() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
var devices = new ArrayList();
devices.add(mDevice);
@@ -317,6 +454,7 @@ public class AudioStreamMediaServiceTest {
@Test
public void assistantCallback_onReceiveStateChanged_connected_doNothing() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
@@ -338,6 +476,7 @@ public class AudioStreamMediaServiceTest {
@Test
public void assistantCallback_onReceiveStateChanged_hysteresis_updateNotification() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
@@ -357,6 +496,7 @@ public class AudioStreamMediaServiceTest {
@Test
public void assistantCallback_onReceiveStateChanged_hysteresis_flagOff_doNothing() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
@@ -386,6 +526,7 @@ public class AudioStreamMediaServiceTest {
@Test
public void bluetoothCallback_onDeviceDisconnect_stopSelf() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
assertThat(mAudioStreamMediaService.mBluetoothCallback).isNotNull();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
@@ -398,9 +539,26 @@ public class AudioStreamMediaServiceTest {
verify(mAudioStreamMediaService).stopSelf();
}
+ @Test
+ public void byReceiveStateFlagOn_bluetoothCallback_onDeviceDisconnect_stopSelf() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
+ mAudioStreamMediaService.onCreate();
+ assertThat(mAudioStreamMediaService.mBluetoothCallback).isNotNull();
+ Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
+ mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
+
+ mAudioStreamMediaService.mBluetoothCallback.onProfileConnectionStateChanged(
+ mCachedBluetoothDevice, BluetoothAdapter.STATE_DISCONNECTED,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+
+ verify(mAudioStreamMediaService).stopSelf();
+ }
+
@Test
public void mediaSessionCallback_onPause_setVolume() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
@@ -415,9 +573,26 @@ public class AudioStreamMediaServiceTest {
eq(1));
}
+ @Test
+ public void byReceiveStateFlagOn_mediaSessionCallback_onPause_setVolume() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
+
+ mAudioStreamMediaService.onCreate();
+ Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
+ mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
+ assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
+ mAudioStreamMediaService.mMediaSessionCallback.onPause();
+
+ verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean());
+ verify(mFeatureFactory.metricsFeatureProvider).action(any(),
+ eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK), eq(1));
+ }
+
@Test
public void mediaSessionCallback_onPlay_setVolume() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
@@ -432,9 +607,66 @@ public class AudioStreamMediaServiceTest {
eq(0));
}
+ @Test
+ public void byReceiveStateFlagOn_mediaSessionCallback_onPlay_setVolume() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
+
+ mAudioStreamMediaService.onCreate();
+ Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
+ mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
+ assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
+ mAudioStreamMediaService.mMediaSessionCallback.onPlay();
+
+ verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean());
+ verify(mFeatureFactory.metricsFeatureProvider).action(any(),
+ eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK), eq(0));
+ }
+
+ @Test
+ public void byReceiveStateFlagOn_mediaSessionCallback_onButtonEventPlay_setVolume() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
+
+ mAudioStreamMediaService.onCreate();
+ Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
+ mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
+ assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
+
+ Intent buttonEvent = new Intent();
+ buttonEvent.putExtra(Intent.EXTRA_KEY_EVENT,
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY));
+ mAudioStreamMediaService.mMediaSessionCallback.onMediaButtonEvent(buttonEvent);
+
+ verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean());
+ verify(mFeatureFactory.metricsFeatureProvider).action(any(),
+ eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK), eq(0));
+ }
+
+ @Test
+ public void byReceiveStateFlagOn_mediaSessionCallback_onButtonEventPause_setVolume() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
+
+ mAudioStreamMediaService.onCreate();
+ Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
+ mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
+ assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
+
+ Intent buttonEvent = new Intent();
+ buttonEvent.putExtra(Intent.EXTRA_KEY_EVENT,
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE));
+ mAudioStreamMediaService.mMediaSessionCallback.onMediaButtonEvent(buttonEvent);
+
+ verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean());
+ verify(mFeatureFactory.metricsFeatureProvider).action(any(),
+ eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK), eq(1));
+ }
+
@Test
public void mediaSessionCallback_onCustomAction_leaveBroadcast() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
mAudioStreamMediaService.onCreate();
mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
@@ -449,6 +681,23 @@ public class AudioStreamMediaServiceTest {
eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_LEAVE_BUTTON_CLICK));
}
+ @Test
+ public void byReceiveStateFlagOn_mediaSessionCallback_onCustomAction_leaveBroadcast() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
+
+ mAudioStreamMediaService.onCreate();
+ Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING);
+ mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0);
+ assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
+ mAudioStreamMediaService.mMediaSessionCallback.onCustomAction(LEAVE_BROADCAST_ACTION,
+ Bundle.EMPTY);
+
+ verify(mAudioStreamsHelper).removeSource(anyInt());
+ verify(mFeatureFactory.metricsFeatureProvider).action(any(),
+ eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_LEAVE_BUTTON_CLICK));
+ }
+
@Test
public void onBind_returnNull() {
IBinder binder = mAudioStreamMediaService.onBind(new Intent());
@@ -466,4 +715,13 @@ public class AudioStreamMediaServiceTest {
intent.putParcelableArrayListExtra(DEVICES, devices);
return intent;
}
+
+ private Intent setupReceiveDataIntent(int broadcastId, BluetoothDevice device,
+ LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState state) {
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice);
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA,
+ new PrivateBroadcastReceiveData(device, 1, broadcastId, "programInfo", state));
+ return intent;
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedStateTest.java
index 59a42a1023d..bda0e6ea21a 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedStateTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedStateTest.java
@@ -22,9 +22,12 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Sou
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -33,6 +36,9 @@ import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
@@ -43,6 +49,7 @@ import com.android.settings.SettingsActivity;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
@@ -62,6 +69,7 @@ import org.robolectric.annotation.Config;
})
public class SourceAddedStateTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final int BROADCAST_ID = 1;
private static final String BROADCAST_TITLE = "title";
private final Context mContext = ApplicationProvider.getApplicationContext();
@@ -105,7 +113,8 @@ public class SourceAddedStateTest {
}
@Test
- public void testPerformAction() {
+ @DisableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE)
+ public void testPerformAction_startService() {
mInstance.setAudioStreamsRepositoryForTesting(mRepository);
BluetoothLeBroadcastMetadata mockMetadata = mock(BluetoothLeBroadcastMetadata.class);
when(mRepository.getCachedMetadata(anyInt())).thenReturn(mockMetadata);
@@ -124,6 +133,27 @@ public class SourceAddedStateTest {
verify(mHelper).startMediaService(eq(mContext), eq(BROADCAST_ID), eq(BROADCAST_TITLE));
}
+ @Test
+ @EnableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE)
+ public void testPerformAction_skipStartService() {
+ mInstance.setAudioStreamsRepositoryForTesting(mRepository);
+ BluetoothLeBroadcastMetadata mockMetadata = mock(BluetoothLeBroadcastMetadata.class);
+ when(mRepository.getCachedMetadata(anyInt())).thenReturn(mockMetadata);
+ when(mPreference.getContext()).thenReturn(mContext);
+ when(mPreference.getSourceOriginForLogging())
+ .thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
+
+ mInstance.performAction(mPreference, mController, mHelper);
+
+ verify(mRepository).saveMetadata(eq(mContext), eq(mockMetadata));
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ eq(mContext),
+ eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED),
+ eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
+ verify(mHelper, never()).startMediaService(any(), anyInt(), anyString());
+ }
+
@Test
public void testGetOnClickListener_startSubSettings() {
when(mController.getFragment()).thenReturn(mFragment);
diff --git a/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsControllerTest.kt b/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsControllerTest.kt
index fb8e4762bbc..5060bd65b33 100644
--- a/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsControllerTest.kt
+++ b/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceDetailsControllerTest.kt
@@ -18,22 +18,30 @@ package com.android.settings.network.ethernet
import android.content.Context
import android.content.ContextWrapper
+import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
@RunWith(AndroidJUnit4::class)
class EthernetInterfaceDetailsControllerTest {
private val ethernetInterfaceDetailsFragment = EthernetInterfaceDetailsFragment()
+ private val lifecycle = mock()
private val context: Context =
object : ContextWrapper(ApplicationProvider.getApplicationContext()) {}
private val ethernetInterfaceDetailsController =
- EthernetInterfaceDetailsController(context, ethernetInterfaceDetailsFragment, "eth0")
+ EthernetInterfaceDetailsController(
+ context,
+ ethernetInterfaceDetailsFragment,
+ "eth0",
+ lifecycle,
+ )
@Test
fun isAvailable_ShouldReturnTrue() {
diff --git a/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTest.kt b/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTest.kt
index 94487be2372..473a4d94635 100644
--- a/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTest.kt
+++ b/tests/robotests/src/com/android/settings/network/ethernet/EthernetInterfaceTest.kt
@@ -18,14 +18,19 @@ package com.android.settings.network.ethernet
import android.content.Context
import android.content.ContextWrapper
+import android.net.ConnectivityManager
import android.net.EthernetManager
import android.net.EthernetManager.STATE_ABSENT
import android.net.EthernetManager.STATE_LINK_DOWN
import android.net.EthernetManager.STATE_LINK_UP
import android.net.IpConfiguration
+import android.net.LinkProperties
+import android.net.Network
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@@ -34,12 +39,15 @@ import org.mockito.kotlin.mock
class EthernetInterfaceTest {
private val mockEthernetManager = mock()
+ private val mockConnectivityManager = mock()
+ private val mockNetwork = mock()
private val context: Context =
object : ContextWrapper(ApplicationProvider.getApplicationContext()) {
override fun getSystemService(name: String): Any? =
when (name) {
Context.ETHERNET_SERVICE -> mockEthernetManager
+ Context.CONNECTIVITY_SERVICE -> mockConnectivityManager
else -> super.getSystemService(name)
}
}
@@ -85,4 +93,27 @@ class EthernetInterfaceTest {
IpConfiguration.IpAssignment.UNASSIGNED,
)
}
+
+ @Test
+ fun linkPropertiesChanged_shouldUpdate() {
+ val linkProperties = LinkProperties()
+ linkProperties.setInterfaceName("eth0")
+ linkProperties.setUsePrivateDns(true)
+
+ ethernetInterface.networkCallback.onLinkPropertiesChanged(mockNetwork, linkProperties)
+
+ assertEquals(ethernetInterface.getLinkProperties().getInterfaceName(), "eth0")
+ assertTrue(ethernetInterface.getLinkProperties().isPrivateDnsActive())
+ }
+
+ @Test
+ fun linkPropertiesChanged_iddoesnotmatch_shouldNotUpdate() {
+ val linkProperties = LinkProperties()
+ linkProperties.setInterfaceName("eth1")
+ linkProperties.setUsePrivateDns(true)
+
+ ethernetInterface.networkCallback.onLinkPropertiesChanged(mockNetwork, linkProperties)
+
+ assertFalse(ethernetInterface.getLinkProperties().isPrivateDnsActive())
+ }
}
diff --git a/tests/robotests/src/com/android/settings/supervision/SupervisionDashboardScreenTest.kt b/tests/robotests/src/com/android/settings/supervision/SupervisionDashboardScreenTest.kt
index d5fa2972513..bf579d9fd89 100644
--- a/tests/robotests/src/com/android/settings/supervision/SupervisionDashboardScreenTest.kt
+++ b/tests/robotests/src/com/android/settings/supervision/SupervisionDashboardScreenTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.settings.supervision
+import android.app.Activity
import android.app.supervision.flags.Flags
import android.content.Context
import android.platform.test.annotations.DisableFlags
@@ -24,6 +25,7 @@ import androidx.fragment.app.testing.FragmentScenario
import androidx.preference.Preference
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.supervision.SupervisionMainSwitchPreference.Companion.REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS
import com.android.settingslib.widget.MainSwitchPreference
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -57,7 +59,7 @@ class SupervisionDashboardScreenTest {
@Test
@EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_SETTINGS_SCREEN)
- fun toggleMainSwitch_disablesChildPreferences() {
+ fun toggleMainSwitch_pinVerificationSucceeded_enablesChildPreferences() {
FragmentScenario.launchInContainer(preferenceScreenCreator.fragmentClass()).onFragment {
fragment ->
val mainSwitchPreference =
@@ -68,8 +70,38 @@ class SupervisionDashboardScreenTest {
assertThat(childPreference.isEnabled).isFalse()
mainSwitchPreference.performClick()
+ // Pretend the PIN verification succeeded.
+ fragment.onActivityResult(
+ requestCode = REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
+ resultCode = Activity.RESULT_OK,
+ data = null,
+ )
assertThat(childPreference.isEnabled).isTrue()
}
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_SETTINGS_SCREEN)
+ fun toggleMainSwitch_pinVerificationFailed_childPreferencesRemainDisabled() {
+ FragmentScenario.launchInContainer(preferenceScreenCreator.fragmentClass()).onFragment {
+ fragment ->
+ val mainSwitchPreference =
+ fragment.findPreference(SupervisionMainSwitchPreference.KEY)!!
+ val childPreference =
+ fragment.findPreference(SupervisionPinManagementScreen.KEY)!!
+
+ assertThat(childPreference.isEnabled).isFalse()
+
+ mainSwitchPreference.performClick()
+ // Pretend the PIN verification failed.
+ fragment.onActivityResult(
+ requestCode = REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
+ resultCode = Activity.RESULT_CANCELED,
+ data = null,
+ )
+
+ assertThat(childPreference.isEnabled).isFalse()
+ }
+ }
}
diff --git a/tests/robotests/src/com/android/settings/supervision/SupervisionMainSwitchPreferenceTest.kt b/tests/robotests/src/com/android/settings/supervision/SupervisionMainSwitchPreferenceTest.kt
index 8b15c29b869..c7c393d9f9e 100644
--- a/tests/robotests/src/com/android/settings/supervision/SupervisionMainSwitchPreferenceTest.kt
+++ b/tests/robotests/src/com/android/settings/supervision/SupervisionMainSwitchPreferenceTest.kt
@@ -15,25 +15,33 @@
*/
package com.android.settings.supervision
+import android.app.Activity
import android.app.supervision.SupervisionManager
import android.content.Context
import android.content.ContextWrapper
+import android.content.Intent
+import androidx.preference.Preference
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.supervision.SupervisionMainSwitchPreference.Companion.REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS
+import com.android.settingslib.metadata.PreferenceLifecycleContext
import com.android.settingslib.preference.createAndBindWidget
import com.android.settingslib.widget.MainSwitchPreference
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class SupervisionMainSwitchPreferenceTest {
- private val preference = SupervisionMainSwitchPreference()
-
+ private val mockLifeCycleContext = mock()
private val mockSupervisionManager = mock()
private val appContext: Context = ApplicationProvider.getApplicationContext()
@@ -46,6 +54,13 @@ class SupervisionMainSwitchPreferenceTest {
}
}
+ private val preference = SupervisionMainSwitchPreference(context)
+
+ @Before
+ fun setUp() {
+ preference.onCreate(mockLifeCycleContext)
+ }
+
@Test
fun checked_supervisionEnabled_returnTrue() {
setSupervisionEnabled(true)
@@ -61,7 +76,7 @@ class SupervisionMainSwitchPreferenceTest {
}
@Test
- fun toggleOn() {
+ fun toggleOn_triggersPinVerification() {
setSupervisionEnabled(false)
val widget = getMainSwitchPreference()
@@ -69,26 +84,90 @@ class SupervisionMainSwitchPreferenceTest {
widget.performClick()
+ verifyConfirmSupervisionCredentialsActivityStarted()
+ assertThat(widget.isChecked).isFalse()
+ verify(mockSupervisionManager, never()).setSupervisionEnabled(false)
+ }
+
+ @Test
+ fun toggleOn_pinVerificationSucceeded_supervisionEnabled() {
+ setSupervisionEnabled(false)
+ val widget = getMainSwitchPreference()
+
+ assertThat(widget.isChecked).isFalse()
+
+ preference.onActivityResult(
+ mockLifeCycleContext,
+ REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
+ Activity.RESULT_OK,
+ null,
+ )
+
assertThat(widget.isChecked).isTrue()
verify(mockSupervisionManager).setSupervisionEnabled(true)
}
@Test
- fun toggleOff() {
+ fun toggleOff_pinVerificationSucceeded_supervisionDisabled() {
setSupervisionEnabled(true)
val widget = getMainSwitchPreference()
assertThat(widget.isChecked).isTrue()
- widget.performClick()
+ preference.onActivityResult(
+ mockLifeCycleContext,
+ REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
+ Activity.RESULT_OK,
+ null,
+ )
assertThat(widget.isChecked).isFalse()
verify(mockSupervisionManager).setSupervisionEnabled(false)
}
- private fun getMainSwitchPreference(): MainSwitchPreference =
- preference.createAndBindWidget(context)
+ @Test
+ fun toggleOff_pinVerificationFailed_supervisionNotEnabled() {
+ setSupervisionEnabled(true)
+ val widget = getMainSwitchPreference()
+
+ assertThat(widget.isChecked).isTrue()
+
+ preference.onActivityResult(
+ mockLifeCycleContext,
+ REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS,
+ Activity.RESULT_CANCELED,
+ null,
+ )
+
+ assertThat(widget.isChecked).isTrue()
+ verify(mockSupervisionManager, never()).setSupervisionEnabled(true)
+ }
private fun setSupervisionEnabled(enabled: Boolean) =
mockSupervisionManager.stub { on { isSupervisionEnabled } doReturn enabled }
+
+ private fun getMainSwitchPreference(): MainSwitchPreference {
+ val widget: MainSwitchPreference = preference.createAndBindWidget(context)
+
+ mockLifeCycleContext.stub {
+ on { findPreference(SupervisionMainSwitchPreference.KEY) } doReturn widget
+ on {
+ requirePreference(SupervisionMainSwitchPreference.KEY)
+ } doReturn widget
+ }
+ return widget
+ }
+
+ private fun verifyConfirmSupervisionCredentialsActivityStarted() {
+ val intentCaptor = argumentCaptor()
+ verify(mockLifeCycleContext)
+ .startActivityForResult(
+ intentCaptor.capture(),
+ eq(REQUEST_CODE_CONFIRM_SUPERVISION_CREDENTIALS),
+ eq(null),
+ )
+ assertThat(intentCaptor.allValues.size).isEqualTo(1)
+ assertThat(intentCaptor.firstValue.component?.className)
+ .isEqualTo(ConfirmSupervisionCredentialsActivity::class.java.name)
+ }
}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appcompat/OWNERS b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/OWNERS
new file mode 100644
index 00000000000..5ef6ded95f1
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 970984
+# Large Screen Experiences App Compat
+gracielawputri@google.com
+mcarli@google.com
+mariiasand@google.com
\ No newline at end of file
diff --git a/tests/unit/src/com/android/settings/applications/appcompat/OWNERS b/tests/unit/src/com/android/settings/applications/appcompat/OWNERS
new file mode 100644
index 00000000000..5ef6ded95f1
--- /dev/null
+++ b/tests/unit/src/com/android/settings/applications/appcompat/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 970984
+# Large Screen Experiences App Compat
+gracielawputri@google.com
+mcarli@google.com
+mariiasand@google.com
\ No newline at end of file
diff --git a/tests/unit/src/com/android/settings/biometrics/ParentalControlsUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/ParentalControlsUtilsTest.java
index 2b6184b9153..2ca90c6c6af 100644
--- a/tests/unit/src/com/android/settings/biometrics/ParentalControlsUtilsTest.java
+++ b/tests/unit/src/com/android/settings/biometrics/ParentalControlsUtilsTest.java
@@ -27,13 +27,12 @@ import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyManager;
import android.app.supervision.SupervisionManager;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.os.UserHandle;
@@ -44,6 +43,7 @@ import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.settingslib.RestrictedLockUtils;
@@ -53,22 +53,29 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
@RunWith(AndroidJUnit4.class)
public class ParentalControlsUtilsTest {
- @Rule public final CheckFlagsRule checkFlags = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final CheckFlagsRule checkFlags = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final MockitoRule mocks = MockitoJUnit.rule();
- @Mock private Context mContext;
- @Mock private DevicePolicyManager mDpm;
- @Mock private SupervisionManager mSm;
+ private Context mContext;
+ @Mock
+ private DevicePolicyManager mDpm;
+ @Mock
+ private SupervisionManager mSm;
- private ComponentName mSupervisionComponentName = new ComponentName("pkg", "cls");
+ private final ComponentName mSupervisionComponent = new ComponentName("pkg", "cls");
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ when(mContext.getSystemService(DevicePolicyManager.class)).thenReturn(mDpm);
+ when(mContext.getSystemService(SupervisionManager.class)).thenReturn(mSm);
}
/**
@@ -85,7 +92,7 @@ public class ParentalControlsUtilsTest {
.thenReturn(keyguardDisabledFlags);
return ParentalControlsUtils.parentConsentRequiredInternal(
- mDpm, mSm, modality, new UserHandle(UserHandle.myUserId()));
+ mContext, modality, new UserHandle(UserHandle.myUserId()));
}
/**
@@ -97,11 +104,13 @@ public class ParentalControlsUtilsTest {
boolean supervisionEnabled,
@BiometricAuthenticator.Modality int modality,
int keyguardDisabledFlags) {
- when(mSm.isSupervisionEnabledForUser(anyInt())).thenReturn(supervisionEnabled);
when(mDpm.getKeyguardDisabledFeatures(eq(null))).thenReturn(keyguardDisabledFlags);
+ when(mSm.isSupervisionEnabledForUser(anyInt())).thenReturn(supervisionEnabled);
+ when(mSm.getActiveSupervisionAppPackage()).thenReturn(
+ supervisionEnabled ? mSupervisionComponent.getPackageName() : null);
return ParentalControlsUtils.parentConsentRequiredInternal(
- mDpm, mSm, modality, new UserHandle(UserHandle.myUserId()));
+ mContext, modality, new UserHandle(UserHandle.myUserId()));
}
@Test
@@ -115,11 +124,11 @@ public class ParentalControlsUtilsTest {
for (int i = 0; i < tests.length; i++) {
RestrictedLockUtils.EnforcedAdmin admin = getEnforcedAdminForCombination(
- mSupervisionComponentName, tests[i][0] /* modality */,
+ mSupervisionComponent, tests[i][0] /* modality */,
tests[i][1] /* keyguardDisableFlags */);
assertNotNull(admin);
assertEquals(UserManager.DISALLOW_BIOMETRIC, admin.enforcedRestriction);
- assertEquals(mSupervisionComponentName, admin.component);
+ assertEquals(mSupervisionComponent, admin.component);
}
}
diff --git a/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java
index f2cf6b9f585..8425f4fa679 100644
--- a/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java
+++ b/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java
@@ -198,6 +198,7 @@ public class CombinedBiometricStatusUtilsTest {
@EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS)
public void getDisabledAdmin_whenFingerprintDisabled_whenFaceDisabled_returnsRestrictions() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
+ when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null))
.thenReturn(KEYGUARD_DISABLE_FACE | KEYGUARD_DISABLE_FINGERPRINT);
diff --git a/tests/unit/src/com/android/settings/biometrics/face/FaceStatusUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/face/FaceStatusUtilsTest.java
index f5d090af72b..e6bfdbd372f 100644
--- a/tests/unit/src/com/android/settings/biometrics/face/FaceStatusUtilsTest.java
+++ b/tests/unit/src/com/android/settings/biometrics/face/FaceStatusUtilsTest.java
@@ -155,6 +155,7 @@ public class FaceStatusUtilsTest {
@EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS)
public void getDisabledAdmin_whenFaceDisabled_returnsRestriction() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
+ when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE);
@@ -172,7 +173,8 @@ public class FaceStatusUtilsTest {
}
@Test
- public void getSummary_whenNotEnrolled_returnsSummaryNone() {
+ @DisableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void getSummary_whenNotEnrolled_flagOff_returnsSummaryNone() {
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
assertThat(mFaceStatusUtils.getSummary())
@@ -181,6 +183,17 @@ public class FaceStatusUtilsTest {
"security_settings_face_preference_summary_none"));
}
+ @Test
+ @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void getSummary_whenNotEnrolled_flagOn_returnsSummaryNone() {
+ when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+
+ assertThat(mFaceStatusUtils.getSummary())
+ .isEqualTo(ResourcesUtils.getResourcesString(
+ mApplicationContext,
+ "security_settings_face_preference_summary_none_new"));
+ }
+
@Test
public void getSummary_whenEnrolled_returnsSummary() {
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
diff --git a/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java
index 375c1ff0c0f..9c77a6d09d6 100644
--- a/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java
+++ b/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java
@@ -161,6 +161,7 @@ public class FingerprintStatusUtilsTest {
@EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS)
public void getDisabledAdmin_whenFingerprintDisabled_returnsRestriction() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
+ when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
@@ -179,7 +180,8 @@ public class FingerprintStatusUtilsTest {
}
@Test
- public void getSummary_whenNotEnrolled_returnsSummaryNone() {
+ @DisableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void getSummary_whenNotEnrolled_flagOff_returnsSummaryNone() {
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
assertThat(mFingerprintStatusUtils.getSummary())
@@ -188,6 +190,17 @@ public class FingerprintStatusUtilsTest {
"security_settings_fingerprint_preference_summary_none"));
}
+ @Test
+ @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void getSummary_whenNotEnrolled_flagOn_returnsSummaryNone() {
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+
+ assertThat(mFingerprintStatusUtils.getSummary())
+ .isEqualTo(ResourcesUtils.getResourcesString(
+ mApplicationContext,
+ "security_settings_fingerprint_preference_summary_none_new"));
+ }
+
@Test
public void getSummary_whenEnrolled_returnsSummary() {
final int enrolledFingerprintsCount = 2;
diff --git a/tests/unit/src/com/android/settings/safetycenter/FaceSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/FaceSafetySourceTest.java
index 9a162ae9a1f..1d6f48f728c 100644
--- a/tests/unit/src/com/android/settings/safetycenter/FaceSafetySourceTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/FaceSafetySourceTest.java
@@ -187,7 +187,7 @@ public class FaceSafetySourceTest {
assertSafetySourceDisabledDataSetWithSingularSummary(
"security_settings_face_preference_title_new",
- "security_settings_face_preference_summary_none");
+ "security_settings_face_preference_summary_none_new");
}
@Test
@@ -195,6 +195,7 @@ public class FaceSafetySourceTest {
@EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS)
public void setSafetySourceData_withFaceNotEnrolled_whenSupervisionIsOn_setsData() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
+ when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
@@ -205,7 +206,7 @@ public class FaceSafetySourceTest {
assertSafetySourceDisabledDataSetWithSingularSummary(
"security_settings_face_preference_title_new",
- "security_settings_face_preference_summary_none");
+ "security_settings_face_preference_summary_none_new");
}
@Test
@@ -220,7 +221,7 @@ public class FaceSafetySourceTest {
assertSafetySourceEnabledDataSetWithSingularSummary(
"security_settings_face_preference_title_new",
- "security_settings_face_preference_summary_none",
+ "security_settings_face_preference_summary_none_new",
FaceEnrollIntroductionInternal.class.getName());
}
@@ -248,6 +249,7 @@ public class FaceSafetySourceTest {
@EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS)
public void setSafetySourceData_withFaceEnrolled_whenSupervisionIsOn_setsData() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
+ when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
diff --git a/tests/unit/src/com/android/settings/safetycenter/FingerprintSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/FingerprintSafetySourceTest.java
index fd109f61638..dac2699ac71 100644
--- a/tests/unit/src/com/android/settings/safetycenter/FingerprintSafetySourceTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/FingerprintSafetySourceTest.java
@@ -203,7 +203,7 @@ public class FingerprintSafetySourceTest {
assertSafetySourceDisabledDataSetWithSingularSummary(
"security_settings_fingerprint",
- "security_settings_fingerprint_preference_summary_none");
+ "security_settings_fingerprint_preference_summary_none_new");
}
@Test
@@ -211,6 +211,7 @@ public class FingerprintSafetySourceTest {
@EnableFlags(android.app.supervision.flags.Flags.FLAG_DEPRECATE_DPM_SUPERVISION_APIS)
public void setSafetySourceData_withFingerprintNotEnrolled_whenSupervisionIsOn_setsData() {
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
+ when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false);
@@ -222,7 +223,7 @@ public class FingerprintSafetySourceTest {
assertSafetySourceDisabledDataSetWithSingularSummary(
"security_settings_fingerprint",
- "security_settings_fingerprint_preference_summary_none");
+ "security_settings_fingerprint_preference_summary_none_new");
}
@Test
@@ -238,7 +239,7 @@ public class FingerprintSafetySourceTest {
assertSafetySourceEnabledDataSetWithSingularSummary(
"security_settings_fingerprint",
- "security_settings_fingerprint_preference_summary_none",
+ "security_settings_fingerprint_preference_summary_none_new",
FingerprintSettings.class.getName());
}
@@ -272,6 +273,7 @@ public class FingerprintSafetySourceTest {
public void setSafetySourceData_withFingerprintsEnrolled_whenSupervisionIsOn_setsData() {
int enrolledFingerprintsCount = 2;
when(mSupervisionManager.isSupervisionEnabledForUser(USER_ID)).thenReturn(true);
+ when(mSupervisionManager.getActiveSupervisionAppPackage()).thenReturn("supervision.pkg");
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true);
diff --git a/tests/unit/src/com/android/settings/safetycenter/OWNERS b/tests/unit/src/com/android/settings/safetycenter/OWNERS
new file mode 100644
index 00000000000..67321c16c74
--- /dev/null
+++ b/tests/unit/src/com/android/settings/safetycenter/OWNERS
@@ -0,0 +1 @@
+include /src/com/android/settings/safetycenter/OWNERS
diff --git a/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java b/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java
index 25358708043..2e674e0b79e 100644
--- a/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java
+++ b/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java
@@ -32,10 +32,13 @@ import android.content.Intent;
import android.content.res.Resources;
import android.os.UserManager;
import android.os.storage.StorageManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
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;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -64,6 +67,8 @@ public class ScreenLockPreferenceDetailsUtilsTest {
private static final int SOURCE_METRICS_CATEGORY = 10;
private static final int USER_ID = 11;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock
@@ -118,7 +123,8 @@ public class ScreenLockPreferenceDetailsUtilsTest {
}
@Test
- public void getSummary_unsecureAndDisabledPattern_shouldReturnUnlockModeOff() {
+ @DisableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void getSummary_unsecureAndDisabledPattern_flagOff_shouldReturnUnlockModeOff() {
final String summary = prepareString("unlock_set_unlock_mode_off", "unlockModeOff");
when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(false);
@@ -127,6 +133,17 @@ public class ScreenLockPreferenceDetailsUtilsTest {
assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary);
}
+ @Test
+ @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void getSummary_unsecureAndDisabledPattern_flagOn_shouldReturnUnlockModeOff() {
+ final String summary = prepareString("unlock_set_unlock_mode_off_new", "unlockModeOff");
+
+ when(mLockPatternUtils.isSecure(USER_ID)).thenReturn(false);
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true);
+
+ assertThat(mScreenLockPreferenceDetailsUtils.getSummary(USER_ID)).isEqualTo(summary);
+ }
+
@Test
public void getSummary_unsecurePattern_shouldReturnUnlockModeNone() {
final String summary =