From 63b80cc9c9c58c69c89961de6e484d62257b7713 Mon Sep 17 00:00:00 2001 From: Mill Chen Date: Wed, 22 Jan 2025 20:09:53 +0000 Subject: [PATCH 01/20] Customize the color of seekbar Bug: 317163103 Test: visual test Flag: EXEMPT style update Change-Id: Iacfec5f1ad4a65b2d0afb75b5b28016f89a21b43 --- res/layout/preference_volume_slider.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/layout/preference_volume_slider.xml b/res/layout/preference_volume_slider.xml index 50095f4f188..1d9a8484920 100644 --- a/res/layout/preference_volume_slider.xml +++ b/res/layout/preference_volume_slider.xml @@ -66,6 +66,8 @@ android:layout_gravity="center_vertical" android:paddingStart="0dp" android:paddingEnd="12dp" + android:progressBackgroundTint="@color/settingslib_materialColorOutline" + android:progressTint="@color/settingslib_materialColorPrimaryFixed" android:layout_width="match_parent" android:layout_height="48dp"/> From 616fbc4f937e3bdff1b26abe13b0ec74f2c3c445 Mon Sep 17 00:00:00 2001 From: Aleksander Morgado Date: Thu, 6 Feb 2025 17:19:22 +0000 Subject: [PATCH 02/20] Added unit tests for PhoneNumberPreferenceController visibility The tests check whether the item is available based on the result of the SubscriptionUtil.isSimHardwareVisible() method, which is the one allowing to check the value of the config_show_sim_info boolean flag on non-test runtime. Bug: 392808943 Flag: EXEMPT test only Test: atest PhoneNumberPreferenceControllerTest Change-Id: I2d1361ecef8a9c1083b4fd6fd4462c7815574827 --- .../PhoneNumberPreferenceControllerTest.kt | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt index 8524ef462fd..bde92507dc3 100644 --- a/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt +++ b/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt @@ -25,20 +25,27 @@ import androidx.preference.PreferenceCategory import androidx.preference.PreferenceManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.settings.R +import com.android.settings.core.BasePreferenceController +import com.android.settings.network.SubscriptionUtil import com.google.common.truth.Truth.assertThat +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.MockitoSession import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness @RunWith(AndroidJUnit4::class) class PhoneNumberPreferenceControllerTest { + private lateinit var mockSession: MockitoSession private val mockTelephonyManager = mock() private val mockSubscriptionManager = mock() @@ -61,6 +68,15 @@ class PhoneNumberPreferenceControllerTest { @Before fun setup() { + mockSession = + ExtendedMockito.mockitoSession() + .mockStatic(SubscriptionUtil::class.java) + .strictness(Strictness.LENIENT) + .startMocking() + + // By default, available + whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true) + preference.setKey(controller.preferenceKey) preference.isVisible = true preferenceScreen.addPreference(preference) @@ -70,6 +86,11 @@ class PhoneNumberPreferenceControllerTest { doReturn(secondPreference).whenever(controller).createNewPreference(context) } + @After + fun teardown() { + mockSession.finishMocking() + } + @Test fun displayPreference_multiSim_shouldAddSecondPreference() { whenever(mockTelephonyManager.phoneCount).thenReturn(2) @@ -132,4 +153,19 @@ class PhoneNumberPreferenceControllerTest { verify(preference).summary = context.getString(R.string.device_info_not_available) } + + @Test + fun getAvailabilityStatus_simHardwareVisible_displayed() { + // Use defaults from setup() + val availabilityStatus = controller.availabilityStatus + assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE) + } + + @Test + fun getAvailabilityStatus_notSimHardwareVisible_notDisplayed() { + whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(false) + + val availabilityStatus = controller.availabilityStatus + assertThat(availabilityStatus).isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE) + } } From 035a15ac374ae934c3697ea3c999108a4af08eb7 Mon Sep 17 00:00:00 2001 From: Aleksander Morgado Date: Thu, 6 Feb 2025 17:30:18 +0000 Subject: [PATCH 03/20] Added unit test for MobileNetworkPhoneNumberPreferenceController visibility The new test checks whether the item is available when the SubscriptionUtil.isSimHardwareVisible() method returns true, i.e. if the config_show_sim_info boolean flag is true on non-test runtime. The default visibility status for all tests is defined in the test setup() method, as this will also simplify the addition of new checks later on. Bug: 392808943 Flag: EXEMPT test only Test: atest MobileNetworkPhoneNumberPreferenceControllerTest Change-Id: Icedef190a8140fd8164dd8035ae93735a666b9f0 --- ...eNetworkPhoneNumberPreferenceControllerTest.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt index f56c0c4b351..a7a87380c8d 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt @@ -61,6 +61,9 @@ class MobileNetworkPhoneNumberPreferenceControllerTest { .strictness(Strictness.LENIENT) .startMocking() + // By default, available + whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true) + preferenceScreen.addPreference(preference) controller.init(SUB_ID) controller.displayPreference(preferenceScreen) @@ -73,7 +76,6 @@ class MobileNetworkPhoneNumberPreferenceControllerTest { @Test fun onViewCreated_cannotGetPhoneNumber_displayUnknown() = runBlocking { - whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true) mockSubscriptionRepository.stub { on { phoneNumberFlow(SUB_ID) } doReturn flowOf(null) } @@ -86,7 +88,6 @@ class MobileNetworkPhoneNumberPreferenceControllerTest { @Test fun onViewCreated_canGetPhoneNumber_displayPhoneNumber() = runBlocking { - whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true) mockSubscriptionRepository.stub { on { phoneNumberFlow(SUB_ID) } doReturn flowOf(PHONE_NUMBER) } @@ -98,11 +99,17 @@ class MobileNetworkPhoneNumberPreferenceControllerTest { } @Test - fun getAvailabilityStatus_notSimHardwareVisible() { + fun getAvailabilityStatus_simHardwareVisible_displayed() { + // Use defaults from setup() + val availabilityStatus = controller.availabilityStatus + assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE) + } + + @Test + fun getAvailabilityStatus_notSimHardwareVisible_notDisplayed() { whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(false) val availabilityStatus = controller.availabilityStatus - assertThat(availabilityStatus).isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE) } From 4fa2836aa5ea067b0885a6cae2704a03b7d76e84 Mon Sep 17 00:00:00 2001 From: pengfeix Date: Tue, 18 Jun 2024 07:02:18 +0530 Subject: [PATCH 04/20] Fix popping up the the VoLTE screen when it's disabled by the carrier. - If only has the default carrier config just return, to avoid popping up the the VoLTE screen when it's disabled by the carrier. Bug: 332647055 Test: manual Flag: NONE bugfix Change-Id: I71bc02f5044535d48726a531e36d57df73bc0f5f --- .../network/telephony/Enhanced4gBasePreferenceController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java b/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java index d1988c4a3b7..62edbf81294 100644 --- a/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java +++ b/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java @@ -107,8 +107,8 @@ public class Enhanced4gBasePreferenceController extends TelephonyTogglePreferenc } final PersistableBundle carrierConfig = getCarrierConfigForSubId(subId); - if ((carrierConfig == null) - || carrierConfig.getBoolean(CarrierConfigManager.KEY_HIDE_ENHANCED_4G_LTE_BOOL)) { + if (!CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig) || + carrierConfig.getBoolean(CarrierConfigManager.KEY_HIDE_ENHANCED_4G_LTE_BOOL)) { return CONDITIONALLY_UNAVAILABLE; } From 7235474792524747913e0a5c4a0bb52bb535de7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Tue, 11 Feb 2025 15:12:06 +0100 Subject: [PATCH 05/20] Inline MODES_API flag Bug: 310620812 Test: Preexisting Flag: EXEMPT Inlining flag Change-Id: I54ad81d87ea4ce85940400b0e8680a1bac0ca342 --- .../ApprovalPreferenceController.java | 10 +---- .../FriendlyWarningDialogFragment.java | 2 +- .../ScaryWarningDialogFragment.java | 2 +- .../FriendlyWarningDialogFragment.java | 4 +- .../zenaccess/ScaryWarningDialogFragment.java | 4 +- .../zenaccess/ZenAccessController.java | 8 +--- .../zenaccess/ZenAccessDetails.java | 4 +- .../DndConditionCardController.java | 9 +---- .../AbstractZenModeHeaderController.java | 2 +- .../modes/ZenSettingsObserver.java | 4 +- ...ModeAutomaticRulePreferenceController.java | 2 +- .../notification/zen/ZenAccessSettings.java | 6 +-- .../notification/zen/ZenModeBackend.java | 40 ++++--------------- .../zen/ZenModeRuleSettingsBase.java | 19 ++++----- .../notification/zen/ZenModeSliceBuilder.java | 9 +---- .../ZenRuleButtonsPreferenceController.java | 6 --- .../notification/zen/ZenRulePreference.java | 3 +- .../zen/ZenRuleSelectionDialog.java | 4 +- .../shortcut/ShortcutsUpdateReceiver.java | 2 +- .../settings/shortcut/ShortcutsUpdater.java | 2 +- .../TopLevelSoundPreferenceController.java | 2 +- .../DndConditionalCardControllerTest.java | 2 +- ...deSetCalendarPreferenceControllerTest.java | 2 +- ...deSetSchedulePreferenceControllerTest.java | 2 +- .../notification/zen/ZenModeBackendTest.java | 18 ++------- .../zen/ZenModeEventRuleSettingsTest.java | 2 +- .../zen/ZenModeScheduleRuleSettingsTest.java | 2 +- .../zen/ZenModeSliceBuilderTest.java | 12 +----- .../ApprovalPreferenceControllerTest.java | 4 -- 29 files changed, 53 insertions(+), 135 deletions(-) diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java index 8ddf9f9571a..21f5a12c18e 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java @@ -22,7 +22,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.os.AsyncTask; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -139,13 +138,8 @@ public class ApprovalPreferenceController extends BasePreferenceController { public void disable(final ComponentName cn) { logSpecialPermissionChange(true, cn.getPackageName()); mNm.setNotificationListenerAccessGranted(cn, false); - if (!mNm.isNotificationPolicyAccessGrantedForPackage( - cn.getPackageName())) { - if (android.app.Flags.modesApi()) { - mNm.removeAutomaticZenRules(cn.getPackageName(), /* fromUser= */ true); - } else { - mNm.removeAutomaticZenRules(cn.getPackageName()); - } + if (!mNm.isNotificationPolicyAccessGrantedForPackage(cn.getPackageName())) { + mNm.removeAutomaticZenRules(cn.getPackageName(), /* fromUser= */ true); } } diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/FriendlyWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/notificationaccess/FriendlyWarningDialogFragment.java index c92f73410b4..9782303b002 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/FriendlyWarningDialogFragment.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/FriendlyWarningDialogFragment.java @@ -56,7 +56,7 @@ public class FriendlyWarningDialogFragment extends InstrumentedDialogFragment { NotificationAccessDetails parent = (NotificationAccessDetails) getTargetFragment(); final String summary = getResources().getString( - Flags.modesApi() && Flags.modesUi() + Flags.modesUi() ? R.string.notification_listener_disable_modes_warning_summary : R.string.notification_listener_disable_warning_summary, label); diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/ScaryWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/notificationaccess/ScaryWarningDialogFragment.java index 53181fd4f9f..2b6e6ca6852 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/ScaryWarningDialogFragment.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/ScaryWarningDialogFragment.java @@ -98,7 +98,7 @@ public class ScaryWarningDialogFragment extends InstrumentedDialogFragment { ((TextView) content.findViewById(R.id.prompt)).setText(prompt); ((TextView) content.findViewById(R.id.settings_description)).setText( - Flags.modesApi() && Flags.modesUi() + Flags.modesUi() ? R.string.nls_feature_modes_settings_summary : R.string.nls_feature_settings_summary); diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java index 38317ed6b52..02c3830ab29 100644 --- a/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java +++ b/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java @@ -59,12 +59,12 @@ public class FriendlyWarningDialogFragment extends InstrumentedDialogFragment { final String label = args.getString(KEY_LABEL); final String title = getResources().getString( - Flags.modesApi() && Flags.modesUi() + Flags.modesUi() ? R.string.zen_modes_access_revoke_warning_dialog_title : R.string.zen_access_revoke_warning_dialog_title, label); final String summary = getResources() - .getString(Flags.modesApi() && Flags.modesUi() + .getString(Flags.modesUi() ? R.string.zen_modes_access_revoke_warning_dialog_summary : R.string.zen_access_revoke_warning_dialog_summary); diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java index b4896026962..73893341829 100644 --- a/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java +++ b/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java @@ -57,12 +57,12 @@ public class ScaryWarningDialogFragment extends InstrumentedDialogFragment { final String label = args.getString(KEY_LABEL); final String title = getResources().getString( - Flags.modesApi() && Flags.modesUi() + Flags.modesUi() ? R.string.zen_modes_access_warning_dialog_title : R.string.zen_access_warning_dialog_title, label); final String summary = getResources() - .getString(Flags.modesApi() && Flags.modesUi() + .getString(Flags.modesUi() ? R.string.zen_modes_access_warning_dialog_summary : R.string.zen_access_warning_dialog_summary); diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java index cfeeb0dc02c..bf3b31dd0a8 100644 --- a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java +++ b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java @@ -56,7 +56,7 @@ public class ZenAccessController extends BasePreferenceController { public void displayPreference(PreferenceScreen screen) { Preference preference = screen.findPreference(getPreferenceKey()); if (preference != null) { - preference.setTitle(Flags.modesApi() && Flags.modesUi() + preference.setTitle(Flags.modesUi() ? R.string.manage_zen_modes_access_title : R.string.manage_zen_access_title); } @@ -116,11 +116,7 @@ public class ZenAccessController extends BasePreferenceController { public static void deleteRules(final Context context, final String pkg) { final NotificationManager mgr = context.getSystemService(NotificationManager.class); - if (android.app.Flags.modesApi()) { - mgr.removeAutomaticZenRules(pkg, /* fromUser= */ true); - } else { - mgr.removeAutomaticZenRules(pkg); - } + mgr.removeAutomaticZenRules(pkg, /* fromUser= */ true); } @VisibleForTesting diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java index 9e71f7a4020..8241594a8d0 100644 --- a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java +++ b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java @@ -51,7 +51,7 @@ public class ZenAccessDetails extends AppInfoWithHeader implements @Override public void onResume() { super.onResume(); - requireActivity().setTitle(Flags.modesApi() && Flags.modesUi() + requireActivity().setTitle(Flags.modesUi() ? R.string.manage_zen_modes_access_title : R.string.manage_zen_access_title); } @@ -89,7 +89,7 @@ public class ZenAccessDetails extends AppInfoWithHeader implements preference.setSummary(getString(R.string.zen_access_disabled_package_warning)); return; } - preference.setTitle(Flags.modesApi() && Flags.modesUi() + preference.setTitle(Flags.modesUi() ? R.string.zen_modes_access_detail_switch : R.string.zen_access_detail_switch); preference.setChecked(ZenAccessController.hasAccess(context, mPackageName)); diff --git a/src/com/android/settings/homepage/contextualcards/conditional/DndConditionCardController.java b/src/com/android/settings/homepage/contextualcards/conditional/DndConditionCardController.java index 314ccec8a80..795420cbee5 100644 --- a/src/com/android/settings/homepage/contextualcards/conditional/DndConditionCardController.java +++ b/src/com/android/settings/homepage/contextualcards/conditional/DndConditionCardController.java @@ -16,7 +16,6 @@ package com.android.settings.homepage.contextualcards.conditional; -import android.app.Flags; import android.app.NotificationManager; import android.app.settings.SettingsEnums; import android.content.BroadcastReceiver; @@ -87,12 +86,8 @@ public class DndConditionCardController implements ConditionalCardController { @Override public void onActionClick() { - if (Flags.modesApi()) { - mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG, - /* fromUser= */ true); - } else { - mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG); - } + mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG, + /* fromUser= */ true); } @Override diff --git a/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java b/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java index 1d1b07d85a6..3d259262f38 100644 --- a/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java +++ b/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java @@ -59,7 +59,7 @@ abstract class AbstractZenModeHeaderController extends AbstractZenModePreference @Override public boolean isAvailable() { - return Flags.modesApi() && Flags.modesUi(); + return Flags.modesUi(); } protected void setUpHeader(PreferenceScreen screen, int iconSizePx) { diff --git a/src/com/android/settings/notification/modes/ZenSettingsObserver.java b/src/com/android/settings/notification/modes/ZenSettingsObserver.java index 0f22d7de341..280d76cf480 100644 --- a/src/com/android/settings/notification/modes/ZenSettingsObserver.java +++ b/src/com/android/settings/notification/modes/ZenSettingsObserver.java @@ -43,7 +43,7 @@ class ZenSettingsObserver extends ContentObserver { } void register() { - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this); mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_ETAG_URI, false, this); @@ -51,7 +51,7 @@ class ZenSettingsObserver extends ContentObserver { } void unregister() { - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { mContext.getContentResolver().unregisterContentObserver(this); } } diff --git a/src/com/android/settings/notification/zen/AbstractZenModeAutomaticRulePreferenceController.java b/src/com/android/settings/notification/zen/AbstractZenModeAutomaticRulePreferenceController.java index 3a809d077fe..59c0a5c7138 100644 --- a/src/com/android/settings/notification/zen/AbstractZenModeAutomaticRulePreferenceController.java +++ b/src/com/android/settings/notification/zen/AbstractZenModeAutomaticRulePreferenceController.java @@ -167,7 +167,7 @@ abstract public class AbstractZenModeAutomaticRulePreferenceController extends mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_ZEN_MODE_RULE_NAME_CHANGE_OK); AutomaticZenRule rule; - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { rule = new AutomaticZenRule.Builder(ruleName, mRuleInfo.defaultConditionId) .setType(mRuleInfo.type) .setOwner(mRuleInfo.serviceComponent) diff --git a/src/com/android/settings/notification/zen/ZenAccessSettings.java b/src/com/android/settings/notification/zen/ZenAccessSettings.java index ebf91bf0284..6727bfb5bb3 100644 --- a/src/com/android/settings/notification/zen/ZenAccessSettings.java +++ b/src/com/android/settings/notification/zen/ZenAccessSettings.java @@ -76,7 +76,7 @@ public class ZenAccessSettings extends EmptyTextSettings implements @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - setEmptyText(Flags.modesApi() && Flags.modesUi() + setEmptyText(Flags.modesUi() ? R.string.zen_modes_access_empty_text : R.string.zen_access_empty_text); } @@ -89,7 +89,7 @@ public class ZenAccessSettings extends EmptyTextSettings implements @Override public void onResume() { super.onResume(); - requireActivity().setTitle(Flags.modesApi() && Flags.modesUi() + requireActivity().setTitle(Flags.modesUi() ? R.string.manage_zen_modes_access_title : R.string.manage_zen_access_title); reloadList(); @@ -145,7 +145,7 @@ public class ZenAccessSettings extends EmptyTextSettings implements pref.setOnPreferenceClickListener(preference -> { AppInfoBase.startAppInfoFragment( ZenAccessDetails.class /* fragment */, - getString(Flags.modesApi() && Flags.modesUi() + getString(Flags.modesUi() ? R.string.manage_zen_modes_access_title : R.string.manage_zen_access_title), pkg, diff --git a/src/com/android/settings/notification/zen/ZenModeBackend.java b/src/com/android/settings/notification/zen/ZenModeBackend.java index 2eb87c5c1aa..093da67d198 100644 --- a/src/com/android/settings/notification/zen/ZenModeBackend.java +++ b/src/com/android/settings/notification/zen/ZenModeBackend.java @@ -95,32 +95,19 @@ public class ZenModeBackend { } protected boolean updateZenRule(String id, AutomaticZenRule rule) { - if (android.app.Flags.modesApi()) { - return mNotificationManager.updateAutomaticZenRule(id, rule, /* fromUser= */ true); - } else { - return NotificationManager.from(mContext).updateAutomaticZenRule(id, rule); - } + return mNotificationManager.updateAutomaticZenRule(id, rule, /* fromUser= */ true); } protected void setZenMode(int zenMode) { - if (android.app.Flags.modesApi()) { - mNotificationManager.setZenMode(zenMode, null, TAG, /* fromUser= */ true); - } else { - NotificationManager.from(mContext).setZenMode(zenMode, null, TAG); - } + mNotificationManager.setZenMode(zenMode, null, TAG, /* fromUser= */ true); mZenMode = getZenMode(); } protected void setZenModeForDuration(int minutes) { Uri conditionId = ZenModeConfig.toTimeCondition(mContext, minutes, ActivityManager.getCurrentUser(), true).id; - if (android.app.Flags.modesApi()) { - mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, - conditionId, TAG, /* fromUser= */ true); - } else { - mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, - conditionId, TAG); - } + mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, + conditionId, TAG, /* fromUser= */ true); mZenMode = getZenMode(); } @@ -190,14 +177,9 @@ public class ZenModeBackend { int priorityConversationSenders) { mPolicy = new NotificationManager.Policy(priorityCategories, priorityCallSenders, priorityMessageSenders, suppressedVisualEffects, priorityConversationSenders); - if (android.app.Flags.modesApi()) { - mNotificationManager.setNotificationPolicy(mPolicy, /* fromUser= */ true); - } else { - mNotificationManager.setNotificationPolicy(mPolicy); - } + mNotificationManager.setNotificationPolicy(mPolicy, /* fromUser= */ true); } - private int getNewSuppressedEffects(boolean suppress, int effectType) { int effects = mPolicy.suppressedVisualEffects; @@ -373,11 +355,7 @@ public class ZenModeBackend { } public boolean removeZenRule(String ruleId) { - if (android.app.Flags.modesApi()) { - return mNotificationManager.removeAutomaticZenRule(ruleId, /* fromUser= */ true); - } else { - return NotificationManager.from(mContext).removeAutomaticZenRule(ruleId); - } + return mNotificationManager.removeAutomaticZenRule(ruleId, /* fromUser= */ true); } public NotificationManager.Policy getConsolidatedPolicy() { @@ -386,11 +364,7 @@ public class ZenModeBackend { protected String addZenRule(AutomaticZenRule rule) { try { - if (android.app.Flags.modesApi()) { - return mNotificationManager.addAutomaticZenRule(rule, /* fromUser= */ true); - } else { - return NotificationManager.from(mContext).addAutomaticZenRule(rule); - } + return mNotificationManager.addAutomaticZenRule(rule, /* fromUser= */ true); } catch (Exception e) { return null; } diff --git a/src/com/android/settings/notification/zen/ZenModeRuleSettingsBase.java b/src/com/android/settings/notification/zen/ZenModeRuleSettingsBase.java index 4c647cce3b9..9e23e367982 100644 --- a/src/com/android/settings/notification/zen/ZenModeRuleSettingsBase.java +++ b/src/com/android/settings/notification/zen/ZenModeRuleSettingsBase.java @@ -104,16 +104,11 @@ public abstract class ZenModeRuleSettingsBase extends ZenModeSettingsBase { Bundle bundle = new Bundle(); bundle.putString(ZenCustomRuleSettings.RULE_ID, mId); - // When modes_api flag is on, we skip the radio button screen distinguishing - // between "default" and "custom" and take users directly to the custom - // settings screen. - String destination = ZenCustomRuleSettings.class.getName(); - int sourceMetricsCategory = 0; - if (Flags.modesApi()) { - // From ZenRuleCustomPolicyPreferenceController#launchCustomSettings - destination = ZenCustomRuleConfigSettings.class.getName(); - sourceMetricsCategory = SettingsEnums.ZEN_CUSTOM_RULE_SOUND_SETTINGS; - } + // Skip the radio button screen distinguishing between "default" and + // "custom" and take users directly to the custom settings screen. + // From ZenRuleCustomPolicyPreferenceController#launchCustomSettings + String destination = ZenCustomRuleConfigSettings.class.getName(); + int sourceMetricsCategory = SettingsEnums.ZEN_CUSTOM_RULE_SOUND_SETTINGS; new SubSettingLauncher(mContext) .setDestination(destination) .setArguments(bundle) @@ -165,7 +160,7 @@ public abstract class ZenModeRuleSettingsBase extends ZenModeSettingsBase { protected void updateScheduleRule(ZenModeConfig.ScheduleInfo schedule) { mRule.setConditionId(ZenModeConfig.toScheduleConditionId(schedule)); - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { mRule.setTriggerDescription( SystemZenRules.getTriggerDescriptionForScheduleTime(mContext, schedule)); } @@ -174,7 +169,7 @@ public abstract class ZenModeRuleSettingsBase extends ZenModeSettingsBase { protected void updateEventRule(ZenModeConfig.EventInfo event) { mRule.setConditionId(ZenModeConfig.toEventConditionId(event)); - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { mRule.setTriggerDescription( SystemZenRules.getTriggerDescriptionForScheduleEvent(mContext, event)); } diff --git a/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java b/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java index d16b1e4ba45..8adeac5f4e8 100644 --- a/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java +++ b/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java @@ -19,7 +19,6 @@ package com.android.settings.notification.zen; import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; import android.annotation.ColorInt; -import android.app.Flags; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.settings.SettingsEnums; @@ -117,12 +116,8 @@ public class ZenModeSliceBuilder { } else { zenMode = Settings.Global.ZEN_MODE_OFF; } - if (Flags.modesApi()) { - NotificationManager.from(context).setZenMode(zenMode, /* conditionId= */ null, TAG, - /* fromUser= */ true); - } else { - NotificationManager.from(context).setZenMode(zenMode, null /* conditionId */, TAG); - } + NotificationManager.from(context).setZenMode(zenMode, /* conditionId= */ null, TAG, + /* fromUser= */ true); // Do not notifyChange on Uri. The service takes longer to update the current value than it // does for the Slice to check the current value again. Let {@link SliceBroadcastRelay} // handle it. diff --git a/src/com/android/settings/notification/zen/ZenRuleButtonsPreferenceController.java b/src/com/android/settings/notification/zen/ZenRuleButtonsPreferenceController.java index 082b2a53ae5..2ae11378c04 100644 --- a/src/com/android/settings/notification/zen/ZenRuleButtonsPreferenceController.java +++ b/src/com/android/settings/notification/zen/ZenRuleButtonsPreferenceController.java @@ -19,19 +19,14 @@ package com.android.settings.notification.zen; import android.app.AutomaticZenRule; import android.app.settings.SettingsEnums; import android.content.Context; -import android.content.Intent; -import android.os.Bundle; import android.text.TextUtils; import android.view.View; import androidx.fragment.app.Fragment; -import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceScreen; -import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; -import com.android.settings.core.SubSettingLauncher; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.widget.ActionButtonsPreference; @@ -87,7 +82,6 @@ public class ZenRuleButtonsPreferenceController extends AbstractZenModePreferenc mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_ZEN_MODE_RULE_NAME_CHANGE_OK); mRule.setName(ruleName); - mRule.setModified(true); mBackend.updateZenRule(mId, mRule); } }); diff --git a/src/com/android/settings/notification/zen/ZenRulePreference.java b/src/com/android/settings/notification/zen/ZenRulePreference.java index 06302134eb3..32b2a8828c0 100644 --- a/src/com/android/settings/notification/zen/ZenRulePreference.java +++ b/src/com/android/settings/notification/zen/ZenRulePreference.java @@ -143,8 +143,7 @@ public class ZenRulePreference extends PrimarySwitchPreference { private String computeRuleSummary(AutomaticZenRule rule) { if (rule != null) { - if (Flags.modesApi() && Flags.modesUi() - && !TextUtils.isEmpty(rule.getTriggerDescription())) { + if (Flags.modesUi() && !TextUtils.isEmpty(rule.getTriggerDescription())) { return rule.getTriggerDescription(); } diff --git a/src/com/android/settings/notification/zen/ZenRuleSelectionDialog.java b/src/com/android/settings/notification/zen/ZenRuleSelectionDialog.java index 48960661972..7a1df0493b8 100644 --- a/src/com/android/settings/notification/zen/ZenRuleSelectionDialog.java +++ b/src/com/android/settings/notification/zen/ZenRuleSelectionDialog.java @@ -181,7 +181,7 @@ public class ZenRuleSelectionDialog extends InstrumentedDialogFragment { rt.title = mContext.getString(R.string.zen_schedule_rule_type_name); rt.packageName = ZenModeConfig.getEventConditionProvider().getPackageName(); rt.defaultConditionId = ZenModeConfig.toScheduleConditionId(schedule); - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { rt.type = AutomaticZenRule.TYPE_SCHEDULE_TIME; rt.defaultTriggerDescription = SystemZenRules.getTriggerDescriptionForScheduleTime( mContext, schedule); @@ -201,7 +201,7 @@ public class ZenRuleSelectionDialog extends InstrumentedDialogFragment { rt.title = mContext.getString(R.string.zen_event_rule_type_name); rt.packageName = ZenModeConfig.getScheduleConditionProvider().getPackageName(); rt.defaultConditionId = ZenModeConfig.toEventConditionId(event); - if (Flags.modesApi() && Flags.modesUi()) { + if (Flags.modesUi()) { rt.type = AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; rt.defaultTriggerDescription = SystemZenRules.getTriggerDescriptionForScheduleEvent( mContext, event); diff --git a/src/com/android/settings/shortcut/ShortcutsUpdateReceiver.java b/src/com/android/settings/shortcut/ShortcutsUpdateReceiver.java index 657af5ba027..3fde390917d 100644 --- a/src/com/android/settings/shortcut/ShortcutsUpdateReceiver.java +++ b/src/com/android/settings/shortcut/ShortcutsUpdateReceiver.java @@ -32,7 +32,7 @@ public class ShortcutsUpdateReceiver extends BroadcastReceiver { @Override public void onReceive(@NonNull Context context, @NonNull Intent intent) { - if (!Flags.modesApi() || !Flags.modesUi()) { + if (!Flags.modesUi()) { return; } diff --git a/src/com/android/settings/shortcut/ShortcutsUpdater.java b/src/com/android/settings/shortcut/ShortcutsUpdater.java index 90a60fda379..30cb5c11aa7 100644 --- a/src/com/android/settings/shortcut/ShortcutsUpdater.java +++ b/src/com/android/settings/shortcut/ShortcutsUpdater.java @@ -86,7 +86,7 @@ public class ShortcutsUpdater { private static ComponentName maybeGetReplacingComponent(Context context, ComponentName cn) { // ZenModeSettingsActivity is replaced by ModesSettingsActivity and will be deleted // soon (so we shouldn't use ZenModeSettingsActivity.class). - if (Flags.modesApi() && Flags.modesUi() + if (Flags.modesUi() && cn.getClassName().endsWith("Settings$ZenModeSettingsActivity")) { return new ComponentName(context, Settings.ModesSettingsActivity.class); } diff --git a/src/com/android/settings/sound/TopLevelSoundPreferenceController.java b/src/com/android/settings/sound/TopLevelSoundPreferenceController.java index ddc33992d8d..c219d7ca740 100644 --- a/src/com/android/settings/sound/TopLevelSoundPreferenceController.java +++ b/src/com/android/settings/sound/TopLevelSoundPreferenceController.java @@ -33,7 +33,7 @@ public class TopLevelSoundPreferenceController extends BasePreferenceController @Override public void updateState(Preference preference) { super.updateState(preference); - preference.setSummary(Flags.modesApi() && Flags.modesUi() + preference.setSummary(Flags.modesUi() ? R.string.sound_dashboard_summary : R.string.sound_dashboard_summary_with_dnd); } diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/DndConditionalCardControllerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/DndConditionalCardControllerTest.java index 6dca4fc32f4..676f84d7a00 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/DndConditionalCardControllerTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/DndConditionalCardControllerTest.java @@ -102,7 +102,7 @@ public class DndConditionalCardControllerTest { private ZenModeConfig getMutedAllConfig() { final ZenModeConfig config = new ZenModeConfig(); config.applyNotificationPolicy(new NotificationManager.Policy(0, 0, 0)); - config.areChannelsBypassingDnd = false; + config.hasPriorityChannels = false; return config; } } diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java index 42aa498c1dc..f0c2369d0a6 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java @@ -85,7 +85,7 @@ public class ZenModeSetCalendarPreferenceControllerTest { } @Test - @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + @EnableFlags(Flags.FLAG_MODES_UI) public void updateEventMode_updatesConditionAndTriggerDescription() { ZenMode mode = new TestModeBuilder() .setPackage(SystemZenRules.PACKAGE_ANDROID) diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java index c378e9de427..b446d716b48 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java @@ -80,7 +80,7 @@ public class ZenModeSetSchedulePreferenceControllerTest { } @Test - @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + @EnableFlags(Flags.FLAG_MODES_UI) public void updateScheduleRule_updatesConditionAndTriggerDescription() { ZenMode mode = new TestModeBuilder() .setPackage(SystemZenRules.PACKAGE_ANDROID) diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeBackendTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeBackendTest.java index 63da3c41ea4..120390d39f3 100644 --- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeBackendTest.java +++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeBackendTest.java @@ -213,11 +213,7 @@ public class ZenModeBackendTest { mBackend.saveConversationSenders(CONVERSATION_SENDERS_NONE); ArgumentCaptor captor = ArgumentCaptor.forClass(Policy.class); - if (android.app.Flags.modesApi()) { - verify(mNotificationManager).setNotificationPolicy(captor.capture(), eq(true)); - } else { - verify(mNotificationManager).setNotificationPolicy(captor.capture()); - } + verify(mNotificationManager).setNotificationPolicy(captor.capture(), eq(true)); Policy expected = new Policy( PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_ALARMS, @@ -241,11 +237,7 @@ public class ZenModeBackendTest { mBackend.saveConversationSenders(CONVERSATION_SENDERS_ANYONE); ArgumentCaptor captor = ArgumentCaptor.forClass(Policy.class); - if (android.app.Flags.modesApi()) { - verify(mNotificationManager).setNotificationPolicy(captor.capture(), eq(true)); - } else { - verify(mNotificationManager).setNotificationPolicy(captor.capture()); - } + verify(mNotificationManager).setNotificationPolicy(captor.capture(), eq(true)); Policy expected = new Policy(PRIORITY_CATEGORY_CONVERSATIONS | PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_ALARMS, @@ -270,11 +262,7 @@ public class ZenModeBackendTest { mBackend.saveSenders(PRIORITY_CATEGORY_CALLS, PRIORITY_SENDERS_ANY); ArgumentCaptor captor = ArgumentCaptor.forClass(Policy.class); - if (android.app.Flags.modesApi()) { - verify(mNotificationManager).setNotificationPolicy(captor.capture(), eq(true)); - } else { - verify(mNotificationManager).setNotificationPolicy(captor.capture()); - } + verify(mNotificationManager).setNotificationPolicy(captor.capture(), eq(true)); Policy expected = new Policy(PRIORITY_CATEGORY_CONVERSATIONS | PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_ALARMS, diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeEventRuleSettingsTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeEventRuleSettingsTest.java index 05c3603b1ca..ef0b862b412 100644 --- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeEventRuleSettingsTest.java +++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeEventRuleSettingsTest.java @@ -107,7 +107,7 @@ public class ZenModeEventRuleSettingsTest { } @Test - @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + @EnableFlags(Flags.FLAG_MODES_UI) public void updateEventRule_updatesConditionAndTriggerDescription() { mFragment.setBackend(mBackend); mFragment.mId = "id"; diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeScheduleRuleSettingsTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeScheduleRuleSettingsTest.java index 90e44e6e193..bb102c0048f 100644 --- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeScheduleRuleSettingsTest.java +++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeScheduleRuleSettingsTest.java @@ -107,7 +107,7 @@ public class ZenModeScheduleRuleSettingsTest { } @Test - @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) + @EnableFlags(Flags.FLAG_MODES_UI) public void updateScheduleRule_updatesConditionAndTriggerDescription() { mFragment.setBackend(mBackend); mFragment.mId = "id"; diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeSliceBuilderTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeSliceBuilderTest.java index 3f9e4864a0c..ddc3d8095a9 100644 --- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeSliceBuilderTest.java +++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeSliceBuilderTest.java @@ -117,11 +117,7 @@ public class ZenModeSliceBuilderTest { ZenModeSliceBuilder.handleUriChange(mContext, intent); - if (android.app.Flags.modesApi()) { - verify(mNm).setZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), any(), any(), eq(true)); - } else { - verify(mNm).setZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), any(), any()); - } + verify(mNm).setZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), any(), any(), eq(true)); } @Test @@ -131,10 +127,6 @@ public class ZenModeSliceBuilderTest { ZenModeSliceBuilder.handleUriChange(mContext, intent); - if (android.app.Flags.modesApi()) { - verify(mNm).setZenMode(eq(ZEN_MODE_OFF), any(), any(), eq(true)); - } else { - verify(mNm).setZenMode(eq(ZEN_MODE_OFF), any(), any()); - } + verify(mNm).setZenMode(eq(ZEN_MODE_OFF), any(), any(), eq(true)); } } diff --git a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java index e488792d55b..bcfaa3d78e1 100644 --- a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java @@ -28,14 +28,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AppOpsManager; -import android.app.Flags; import android.app.NotificationManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -50,7 +48,6 @@ import com.android.settingslib.RestrictedSwitchPreference; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -188,7 +185,6 @@ public class ApprovalPreferenceControllerTest { } @Test - @EnableFlags(Flags.FLAG_MODES_API) public void disable() { when(mNm.isNotificationPolicyAccessGrantedForPackage(anyString())).thenReturn(false); mController.disable(mCn); From 571103b8913f7864647c9d2768c6ba2dc21029fa Mon Sep 17 00:00:00 2001 From: Aleksander Morgado Date: Thu, 6 Feb 2025 17:49:40 +0000 Subject: [PATCH 06/20] Phone number only available for admin users The phone number is hidden to non-admin users, following the same reasoning that exists for other telephony-specific fields like the baseband IMEI. Bug: 392808943 Flag: EXEMPT bugfix Test: atest PhoneNumberPreferenceControllerTest Test: atest MobileNetworkPhoneNumberPreferenceControllerTest Change-Id: I4e612219d0c7439930e91b3e1d6e368a0dfd073e --- .../PhoneNumberPreferenceController.java | 10 +++++-- ...eNetworkPhoneNumberPreferenceController.kt | 15 ++++++----- .../PhoneNumberPreferenceControllerTest.kt | 25 ++++++++++++++---- ...workPhoneNumberPreferenceControllerTest.kt | 26 ++++++++++++++++--- 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java index 421963eb31c..12a45dea79c 100644 --- a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java +++ b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java @@ -17,6 +17,7 @@ package com.android.settings.deviceinfo; import android.content.Context; +import android.os.UserManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -51,8 +52,13 @@ public class PhoneNumberPreferenceController extends BasePreferenceController { @Override public int getAvailabilityStatus() { - return SubscriptionUtil.isSimHardwareVisible(mContext) ? - AVAILABLE : UNSUPPORTED_ON_DEVICE; + if (!SubscriptionUtil.isSimHardwareVisible(mContext)) { + return UNSUPPORTED_ON_DEVICE; + } + if (!mContext.getSystemService(UserManager.class).isAdminUser()) { + return DISABLED_FOR_USER; + } + return AVAILABLE; } @Override diff --git a/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt b/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt index db16acdfc59..c62599969dc 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt +++ b/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt @@ -25,6 +25,7 @@ import com.android.settings.R import com.android.settings.flags.Flags import com.android.settings.network.SubscriptionUtil import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle +import com.android.settingslib.spaprivileged.framework.common.userManager /** Preference controller for "Phone number" */ class MobileNetworkPhoneNumberPreferenceController @@ -41,13 +42,13 @@ constructor( mSubId = subId } - override fun getAvailabilityStatus(subId: Int): Int = - when { - !Flags.isDualSimOnboardingEnabled() -> CONDITIONALLY_UNAVAILABLE - SubscriptionManager.isValidSubscriptionId(subId) && - SubscriptionUtil.isSimHardwareVisible(mContext) -> AVAILABLE - else -> CONDITIONALLY_UNAVAILABLE - } + override fun getAvailabilityStatus(subId: Int): Int = when { + !Flags.isDualSimOnboardingEnabled() + || !SubscriptionManager.isValidSubscriptionId(subId) + || !SubscriptionUtil.isSimHardwareVisible(mContext) -> CONDITIONALLY_UNAVAILABLE + !mContext.userManager.isAdminUser -> DISABLED_FOR_USER + else -> AVAILABLE + } override fun displayPreference(screen: PreferenceScreen) { super.displayPreference(screen) diff --git a/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt index bde92507dc3..d9470469401 100644 --- a/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt +++ b/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt @@ -17,6 +17,7 @@ package com.android.settings.deviceinfo import android.content.Context +import android.os.UserManager import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.TelephonyManager @@ -39,6 +40,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.spy +import org.mockito.kotlin.stub import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.quality.Strictness @@ -47,15 +49,15 @@ import org.mockito.quality.Strictness class PhoneNumberPreferenceControllerTest { private lateinit var mockSession: MockitoSession + private val mockUserManager = mock() private val mockTelephonyManager = mock() private val mockSubscriptionManager = mock() private val context: Context = spy(ApplicationProvider.getApplicationContext()) { - on { getSystemService(SubscriptionManager::class.java) } doReturn - mockSubscriptionManager - + on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + on { getSystemService(UserManager::class.java) } doReturn mockUserManager } private val subscriptionInfo = mock() @@ -76,6 +78,9 @@ class PhoneNumberPreferenceControllerTest { // By default, available whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true) + mockUserManager.stub { + on { isAdminUser } doReturn true + } preference.setKey(controller.preferenceKey) preference.isVisible = true @@ -155,17 +160,27 @@ class PhoneNumberPreferenceControllerTest { } @Test - fun getAvailabilityStatus_simHardwareVisible_displayed() { + fun getAvailabilityStatus_simHardwareVisible_userAdmin_displayed() { // Use defaults from setup() val availabilityStatus = controller.availabilityStatus assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE) } @Test - fun getAvailabilityStatus_notSimHardwareVisible_notDisplayed() { + fun getAvailabilityStatus_notSimHardwareVisible_userAdmin_notDisplayed() { whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(false) val availabilityStatus = controller.availabilityStatus assertThat(availabilityStatus).isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE) } + + @Test + fun getAvailabilityStatus_simHardwareVisible_notUserAdmin_notDisplayed() { + mockUserManager.stub { + on { isAdminUser } doReturn false + } + + val availabilityStatus = controller.availabilityStatus + assertThat(availabilityStatus).isEqualTo(BasePreferenceController.DISABLED_FOR_USER) + } } diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt index a7a87380c8d..c6bd0bdcc85 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt @@ -17,6 +17,7 @@ package com.android.settings.network.telephony import android.content.Context +import android.os.UserManager import androidx.lifecycle.testing.TestLifecycleOwner import androidx.preference.Preference import androidx.preference.PreferenceManager @@ -37,6 +38,7 @@ import org.junit.runner.RunWith import org.mockito.MockitoSession import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock +import org.mockito.kotlin.spy import org.mockito.kotlin.stub import org.mockito.kotlin.whenever import org.mockito.quality.Strictness @@ -45,9 +47,14 @@ import org.mockito.quality.Strictness class MobileNetworkPhoneNumberPreferenceControllerTest { private lateinit var mockSession: MockitoSession - private val context: Context = ApplicationProvider.getApplicationContext() + private val mockUserManager = mock() private val mockSubscriptionRepository = mock() + private val context: Context = + spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(UserManager::class.java) } doReturn mockUserManager + } + private val controller = MobileNetworkPhoneNumberPreferenceController(context, TEST_KEY, mockSubscriptionRepository) private val preference = Preference(context).apply { key = TEST_KEY } @@ -63,6 +70,9 @@ class MobileNetworkPhoneNumberPreferenceControllerTest { // By default, available whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true) + mockUserManager.stub { + on { isAdminUser } doReturn true + } preferenceScreen.addPreference(preference) controller.init(SUB_ID) @@ -99,20 +109,30 @@ class MobileNetworkPhoneNumberPreferenceControllerTest { } @Test - fun getAvailabilityStatus_simHardwareVisible_displayed() { + fun getAvailabilityStatus_simHardwareVisible_userAdmin_displayed() { // Use defaults from setup() val availabilityStatus = controller.availabilityStatus assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE) } @Test - fun getAvailabilityStatus_notSimHardwareVisible_notDisplayed() { + fun getAvailabilityStatus_notSimHardwareVisible_userAdmin_notDisplayed() { whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(false) val availabilityStatus = controller.availabilityStatus assertThat(availabilityStatus).isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE) } + @Test + fun getAvailabilityStatus_simHardwareVisible_notUserAdmin_notDisplayed() { + mockUserManager.stub { + on { isAdminUser } doReturn false + } + + val availabilityStatus = controller.availabilityStatus + assertThat(availabilityStatus).isEqualTo(BasePreferenceController.DISABLED_FOR_USER) + } + private companion object { const val TEST_KEY = "test_key" const val SUB_ID = 10 From 731df0fa98d1824c15dc261e354f5e166034a6ec Mon Sep 17 00:00:00 2001 From: Aleksander Morgado Date: Thu, 6 Feb 2025 18:14:32 +0000 Subject: [PATCH 07/20] Phone number only available if telephony capable The Phone Number field in either the about or network details pages should not be shown if the device does not have telephony capabilities. Bug: 392808943 Flag: EXEMPT bugfix Test: atest PhoneNumberPreferenceControllerTest Test: atest MobileNetworkPhoneNumberPreferenceControllerTest Change-Id: I04f682cc829d5dc7879e6cdacdaebc55a3b6fd2c --- .../PhoneNumberPreferenceController.java | 3 ++- ...ileNetworkPhoneNumberPreferenceController.kt | 4 +++- .../PhoneNumberPreferenceControllerTest.kt | 17 ++++++++++++++--- ...etworkPhoneNumberPreferenceControllerTest.kt | 17 ++++++++++++++--- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java index 12a45dea79c..b49d62d444f 100644 --- a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java +++ b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java @@ -31,6 +31,7 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.network.SubscriptionUtil; +import com.android.settingslib.Utils; import java.util.ArrayList; import java.util.List; @@ -52,7 +53,7 @@ public class PhoneNumberPreferenceController extends BasePreferenceController { @Override public int getAvailabilityStatus() { - if (!SubscriptionUtil.isSimHardwareVisible(mContext)) { + if (!SubscriptionUtil.isSimHardwareVisible(mContext) || Utils.isWifiOnly(mContext)) { return UNSUPPORTED_ON_DEVICE; } if (!mContext.getSystemService(UserManager.class).isAdminUser()) { diff --git a/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt b/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt index c62599969dc..40cb6f93ace 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt +++ b/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceController.kt @@ -26,6 +26,7 @@ import com.android.settings.flags.Flags import com.android.settings.network.SubscriptionUtil import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import com.android.settingslib.spaprivileged.framework.common.userManager +import com.android.settingslib.Utils /** Preference controller for "Phone number" */ class MobileNetworkPhoneNumberPreferenceController @@ -45,7 +46,8 @@ constructor( override fun getAvailabilityStatus(subId: Int): Int = when { !Flags.isDualSimOnboardingEnabled() || !SubscriptionManager.isValidSubscriptionId(subId) - || !SubscriptionUtil.isSimHardwareVisible(mContext) -> CONDITIONALLY_UNAVAILABLE + || !SubscriptionUtil.isSimHardwareVisible(mContext) + || Utils.isWifiOnly(mContext) -> CONDITIONALLY_UNAVAILABLE !mContext.userManager.isAdminUser -> DISABLED_FOR_USER else -> AVAILABLE } diff --git a/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt index d9470469401..ed40a8c87ac 100644 --- a/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt +++ b/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt @@ -30,6 +30,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.settings.R import com.android.settings.core.BasePreferenceController import com.android.settings.network.SubscriptionUtil +import com.android.settingslib.Utils import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before @@ -73,11 +74,13 @@ class PhoneNumberPreferenceControllerTest { mockSession = ExtendedMockito.mockitoSession() .mockStatic(SubscriptionUtil::class.java) + .mockStatic(Utils::class.java) .strictness(Strictness.LENIENT) .startMocking() // By default, available whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true) + whenever(Utils.isWifiOnly(context)).thenReturn(false) mockUserManager.stub { on { isAdminUser } doReturn true } @@ -160,14 +163,14 @@ class PhoneNumberPreferenceControllerTest { } @Test - fun getAvailabilityStatus_simHardwareVisible_userAdmin_displayed() { + fun getAvailabilityStatus_simHardwareVisible_userAdmin_notWifiOnly_displayed() { // Use defaults from setup() val availabilityStatus = controller.availabilityStatus assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE) } @Test - fun getAvailabilityStatus_notSimHardwareVisible_userAdmin_notDisplayed() { + fun getAvailabilityStatus_notSimHardwareVisible_userAdmin_notWifiOnly_notDisplayed() { whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(false) val availabilityStatus = controller.availabilityStatus @@ -175,7 +178,7 @@ class PhoneNumberPreferenceControllerTest { } @Test - fun getAvailabilityStatus_simHardwareVisible_notUserAdmin_notDisplayed() { + fun getAvailabilityStatus_simHardwareVisible_notUserAdmin_notWifiOnly_notDisplayed() { mockUserManager.stub { on { isAdminUser } doReturn false } @@ -183,4 +186,12 @@ class PhoneNumberPreferenceControllerTest { val availabilityStatus = controller.availabilityStatus assertThat(availabilityStatus).isEqualTo(BasePreferenceController.DISABLED_FOR_USER) } + + @Test + fun getAvailabilityStatus_simHardwareVisible_userAdmin_wifiOnly_notDisplayed() { + whenever(Utils.isWifiOnly(context)).thenReturn(true) + + val availabilityStatus = controller.availabilityStatus + assertThat(availabilityStatus).isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE) + } } diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt index c6bd0bdcc85..a46b71033b6 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkPhoneNumberPreferenceControllerTest.kt @@ -27,6 +27,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.settings.R import com.android.settings.core.BasePreferenceController import com.android.settings.network.SubscriptionUtil +import com.android.settingslib.Utils import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flowOf @@ -65,11 +66,13 @@ class MobileNetworkPhoneNumberPreferenceControllerTest { mockSession = ExtendedMockito.mockitoSession() .mockStatic(SubscriptionUtil::class.java) + .mockStatic(Utils::class.java) .strictness(Strictness.LENIENT) .startMocking() // By default, available whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(true) + whenever(Utils.isWifiOnly(context)).thenReturn(false) mockUserManager.stub { on { isAdminUser } doReturn true } @@ -109,14 +112,14 @@ class MobileNetworkPhoneNumberPreferenceControllerTest { } @Test - fun getAvailabilityStatus_simHardwareVisible_userAdmin_displayed() { + fun getAvailabilityStatus_simHardwareVisible_userAdmin_notWifiOnly_displayed() { // Use defaults from setup() val availabilityStatus = controller.availabilityStatus assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE) } @Test - fun getAvailabilityStatus_notSimHardwareVisible_userAdmin_notDisplayed() { + fun getAvailabilityStatus_notSimHardwareVisible_userAdmin_notWifiOnly_notDisplayed() { whenever(SubscriptionUtil.isSimHardwareVisible(context)).thenReturn(false) val availabilityStatus = controller.availabilityStatus @@ -124,7 +127,7 @@ class MobileNetworkPhoneNumberPreferenceControllerTest { } @Test - fun getAvailabilityStatus_simHardwareVisible_notUserAdmin_notDisplayed() { + fun getAvailabilityStatus_simHardwareVisible_notUserAdmin_notWifiOnly_notDisplayed() { mockUserManager.stub { on { isAdminUser } doReturn false } @@ -133,6 +136,14 @@ class MobileNetworkPhoneNumberPreferenceControllerTest { assertThat(availabilityStatus).isEqualTo(BasePreferenceController.DISABLED_FOR_USER) } + @Test + fun getAvailabilityStatus_simHardwareVisible_userAdmin_wifiOnly_notDisplayed() { + whenever(Utils.isWifiOnly(context)).thenReturn(true) + + val availabilityStatus = controller.availabilityStatus + assertThat(availabilityStatus).isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE) + } + private companion object { const val TEST_KEY = "test_key" const val SUB_ID = 10 From 09841d6bb44e34e9a175f009d94e76ce4d9925a0 Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Wed, 5 Feb 2025 11:44:11 +0000 Subject: [PATCH 08/20] [Settings] Apply new style to dialog Bug: 394542699 Test: manual Test: atest SettingsRoboTests:com.android.settings.localepicker.LocaleListEditorTest Flag: EXEMPT refactor Change-Id: Idfed52722d1113e432742342fd8a56958e84406e --- res/drawable/ic_settings_globe.xml | 21 --- res/drawable/ic_settings_language_32dp.xml | 25 +++ res/values/dimens.xml | 6 + res/values/strings.xml | 9 +- .../localepicker/LocaleDialogFragment.java | 100 ++++++----- .../localepicker/LocaleListEditor.java | 157 +++++++++--------- .../LocaleDialogFragmentTest.java | 12 +- .../localepicker/LocaleListEditorTest.java | 35 ++-- .../LocaleDialogFragmentTest.java | 12 +- 9 files changed, 199 insertions(+), 178 deletions(-) delete mode 100644 res/drawable/ic_settings_globe.xml create mode 100644 res/drawable/ic_settings_language_32dp.xml diff --git a/res/drawable/ic_settings_globe.xml b/res/drawable/ic_settings_globe.xml deleted file mode 100644 index 9834df6adba..00000000000 --- a/res/drawable/ic_settings_globe.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - \ No newline at end of file diff --git a/res/drawable/ic_settings_language_32dp.xml b/res/drawable/ic_settings_language_32dp.xml new file mode 100644 index 00000000000..b603d55598f --- /dev/null +++ b/res/drawable/ic_settings_language_32dp.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 3bdea5abfe5..738374d4591 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -554,4 +554,10 @@ 5dp 2dp 10dp + + + 10dp + 16dp + 24dp + 32dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 80b0e8aad8e..21b4aac2735 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -502,7 +502,7 @@ Your device settings and regional preferences will change. - + Change @@ -530,10 +530,11 @@ More language settings - Change region to %s ? + Change region to %1$s ? - Your device will keep %s as a system language - The digits used will be dependent on the numbering system + Your device will keep %1$s as a system language + + Most apps will use your regional preferences diff --git a/src/com/android/settings/localepicker/LocaleDialogFragment.java b/src/com/android/settings/localepicker/LocaleDialogFragment.java index 91cbc87ee2e..a3a4b8fee72 100644 --- a/src/com/android/settings/localepicker/LocaleDialogFragment.java +++ b/src/com/android/settings/localepicker/LocaleDialogFragment.java @@ -29,19 +29,20 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.TextView; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import androidx.appcompat.app.AlertDialog; import com.android.internal.app.LocaleStore; import com.android.settings.R; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settingslib.utils.CustomDialogHelper; /** * Create a dialog for system locale events. @@ -58,7 +59,6 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment { static final String ARG_SHOW_DIALOG = "arg_show_dialog"; private boolean mShouldKeepDialog; - private AlertDialog mAlertDialog; private OnBackInvokedDispatcher mBackDispatcher; private OnBackInvokedCallback mBackCallback = () -> { @@ -106,45 +106,53 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment { LocaleListEditor parentFragment = (LocaleListEditor) getParentFragment(); LocaleDialogController controller = getLocaleDialogController(getContext(), this, parentFragment); - LocaleDialogController.DialogContent dialogContent = controller.getDialogContent(); - ViewGroup viewGroup = (ViewGroup) LayoutInflater.from(getContext()).inflate( - R.layout.locale_dialog, null); - setDialogTitle(viewGroup, dialogContent.mTitle); - setDialogMessage(viewGroup, dialogContent.mMessage); - - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()) - .setView(viewGroup); - if (!dialogContent.mPositiveButton.isEmpty()) { - builder.setPositiveButton(dialogContent.mPositiveButton, controller); - } - if (!dialogContent.mNegativeButton.isEmpty()) { - builder.setNegativeButton(dialogContent.mNegativeButton, controller); - } - mAlertDialog = builder.create(); - getOnBackInvokedDispatcher().registerOnBackInvokedCallback(PRIORITY_DEFAULT, mBackCallback); - mAlertDialog.setCanceledOnTouchOutside(false); - mAlertDialog.setOnDismissListener(dialogInterface -> { - mAlertDialog.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback( - mBackCallback); + Dialog dialog = createDialog(getContext(), controller); + dialog.setCanceledOnTouchOutside(false); + getOnBackInvokedDispatcher(dialog).registerOnBackInvokedCallback(PRIORITY_DEFAULT, + mBackCallback); + dialog.setOnDismissListener(dialogInterface -> { + getOnBackInvokedDispatcher(dialog).unregisterOnBackInvokedCallback( + mBackCallback); }); - return mAlertDialog; + return dialog; } - private static void setDialogTitle(View root, String content) { - TextView titleView = root.findViewById(R.id.dialog_title); - if (titleView == null) { - return; + private Dialog createDialog(Context context, LocaleDialogController controller) { + CustomDialogHelper dialogHelper = new CustomDialogHelper(context); + LocaleDialogController.DialogContent dialogContent = controller.getDialogContent(); + dialogHelper.setIcon(context.getDrawable(R.drawable.ic_settings_language_32dp)) + .setTitle(dialogContent.mTitle) + .setMessage(dialogContent.mMessage) + .setIconPadding(0, + context.getResources().getDimensionPixelSize( + R.dimen.locale_picker_dialog_icon_padding), + 0, 0) + .setTitlePadding(0, + context.getResources().getDimensionPixelSize( + R.dimen.locale_picker_dialog_title_padding), + 0, + context.getResources().getDimensionPixelSize( + R.dimen.locale_picker_dialog_title_padding)) + .setMessagePadding(context.getResources().getDimensionPixelSize( + R.dimen.locale_picker_dialog_message_padding_left_right), 0, + context.getResources().getDimensionPixelSize( + R.dimen.locale_picker_dialog_message_padding_left_right), + context.getResources().getDimensionPixelSize( + R.dimen.locale_picker_dialog_message_padding_bottom)) + .setPositiveButton(dialogContent.mPositiveButton, + view -> { + controller.onClick(dialogHelper.getDialog(), + DialogInterface.BUTTON_POSITIVE); + dialogHelper.getDialog().dismiss(); + }); + if (dialogContent.mNegativeButton != 0) { + dialogHelper.setBackButton(dialogContent.mNegativeButton, view -> { + controller.onClick(dialogHelper.getDialog(), DialogInterface.BUTTON_NEGATIVE); + dialogHelper.getDialog().dismiss(); + }); } - titleView.setText(content); - } - - private static void setDialogMessage(View root, String content) { - TextView textView = root.findViewById(R.id.dialog_msg); - if (textView == null) { - return; - } - textView.setText(content); + return dialogHelper.getDialog(); } @VisibleForTesting @@ -158,11 +166,11 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment { } @VisibleForTesting - public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { + public @NonNull OnBackInvokedDispatcher getOnBackInvokedDispatcher(@NonNull Dialog dialog) { if (mBackDispatcher != null) { return mBackDispatcher; } else { - return mAlertDialog.getOnBackInvokedDispatcher(); + return dialog.getOnBackInvokedDispatcher(); } } @@ -223,15 +231,15 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment { R.string.title_change_system_locale), mLocaleInfo.getFullNameNative()); dialogContent.mMessage = mContext.getString( R.string.desc_notice_device_locale_settings_change); - dialogContent.mPositiveButton = mContext.getString( - R.string.button_label_confirmation_of_system_locale_change); - dialogContent.mNegativeButton = mContext.getString(R.string.cancel); + dialogContent.mPositiveButton = + R.string.button_label_confirmation_of_system_locale_change; + dialogContent.mNegativeButton = R.string.cancel; break; case DIALOG_NOT_AVAILABLE_LOCALE: dialogContent.mTitle = String.format(mContext.getString( R.string.title_unavailable_locale), mLocaleInfo.getFullNameNative()); dialogContent.mMessage = mContext.getString(R.string.desc_unavailable_locale); - dialogContent.mPositiveButton = mContext.getString(R.string.okay); + dialogContent.mPositiveButton = R.string.okay; break; case DIALOG_ADD_SYSTEM_LOCALE: dialogContent.mTitle = String.format(mContext.getString( @@ -239,8 +247,8 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment { mLocaleInfo.getFullNameNative()); dialogContent.mMessage = mContext.getString( R.string.desc_system_locale_addition); - dialogContent.mPositiveButton = mContext.getString(R.string.add); - dialogContent.mNegativeButton = mContext.getString(R.string.cancel); + dialogContent.mPositiveButton = R.string.add; + dialogContent.mNegativeButton = R.string.cancel; break; default: break; @@ -252,8 +260,8 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment { static class DialogContent { String mTitle = ""; String mMessage = ""; - String mPositiveButton = ""; - String mNegativeButton = ""; + int mPositiveButton = 0; + int mNegativeButton = 0; } } } diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java index f9c50b79bd7..461ea6587b9 100644 --- a/src/com/android/settings/localepicker/LocaleListEditor.java +++ b/src/com/android/settings/localepicker/LocaleListEditor.java @@ -43,8 +43,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceScreen; import androidx.recyclerview.widget.RecyclerView; @@ -57,6 +57,7 @@ import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.search.SearchIndexableRaw; +import com.android.settingslib.utils.CustomDialogHelper; import com.android.settingslib.utils.StringUtil; import com.android.settingslib.widget.LayoutPreference; @@ -69,6 +70,8 @@ import java.util.Locale; */ @SearchIndexable public class LocaleListEditor extends RestrictedSettingsFragment implements View.OnTouchListener { + public static final int REQUEST_LOCALE_PICKER = 0; + protected static final String INTENT_LOCALE_KEY = "localeInfo"; protected static final String EXTRA_SYSTEM_LOCALE_DIALOG_TYPE = "system_locale_dialog_type"; protected static final String EXTRA_RESULT_LOCALE = "result_locale"; @@ -85,12 +88,10 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View private static final String TAG_DIALOG_NOT_AVAILABLE = "dialog_not_available_locale"; private static final String TAG_DIALOG_ADD_SYSTEM_LOCALE = "dialog_add_system_locale"; private static final int MENU_ID_REMOVE = Menu.FIRST + 1; - private static final int REQUEST_LOCALE_PICKER = 0; private LocaleDragAndDropAdapter mAdapter; private Menu mMenu; private View mAddLanguage; - private AlertDialog mSuggestionDialog = null; private boolean mRemoveMode; private boolean mShowingRemoveDialog; private boolean mLocaleAdditionMode = false; @@ -330,7 +331,6 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View private void showDialogForAddedLocale() { Log.d(TAG, "show confirmation dialog"); Intent intent = this.getIntent(); - String dialogType = intent.getStringExtra(EXTRA_SYSTEM_LOCALE_DIALOG_TYPE); String appLocaleTag = intent.getStringExtra(EXTRA_APP_LOCALE); LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo( @@ -344,17 +344,6 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View localeDialogFragment.show(mFragmentManager, TAG_DIALOG_ADD_SYSTEM_LOCALE); } - private void customizeLayout(AlertDialog.Builder dialogBuilder, String language) { - View dialogView = getLocaleDialogView(); - dialogBuilder.setView(dialogView); - TextView title = dialogView.findViewById(R.id.dialog_title); - title.setText( - String.format(getContext().getResources().getString( - R.string.title_system_locale_addition), language)); - TextView message = dialogView.findViewById(R.id.dialog_msg); - message.setText(R.string.desc_system_locale_addition); - } - protected View getLocaleDialogView() { LayoutInflater inflater = this.getLayoutInflater(); return inflater.inflate(R.layout.locale_dialog, null); @@ -374,25 +363,33 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View return; } + int messagePaddingLeftRight = getContext().getResources().getDimensionPixelSize( + R.dimen.locale_picker_dialog_message_padding_left_right); + int messagePaddingBottom = getContext().getResources().getDimensionPixelSize( + R.dimen.locale_picker_dialog_message_padding_bottom); // All locales selected, warning dialog, can't remove them all if (checkedCount == mAdapter.getItemCount()) { mShowingRemoveDialog = true; - new AlertDialog.Builder(getActivity()) - .setTitle(R.string.dlg_remove_locales_error_title) - .setMessage(R.string.dlg_remove_locales_error_message) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - } - }) - .setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - mShowingRemoveDialog = false; - } - }) - .create() - .show(); + + CustomDialogHelper dialogHelper = createRegionDialog(getContext(), + getContext().getString(R.string.dlg_remove_locales_error_title)); + dialogHelper.setMessage(R.string.dlg_remove_locales_error_message) + .setMessagePadding(messagePaddingLeftRight, 0, messagePaddingLeftRight, + messagePaddingBottom) + .setPositiveButton(android.R.string.ok, + view -> { + dialogHelper.getDialog().dismiss(); + }) + .setBackButton(R.string.cancel, view -> { + dialogHelper.getDialog().dismiss(); + }); + dialogHelper.getDialog().setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + mShowingRemoveDialog = false; + } + }); + dialogHelper.getDialog().show(); return; } @@ -400,54 +397,63 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View R.string.dlg_remove_locales_title); mShowingRemoveDialog = true; - final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + CustomDialogHelper dialogHelper = createRegionDialog(getContext(), title); if (mAdapter.isFirstLocaleChecked()) { - builder.setMessage(R.string.dlg_remove_locales_message); + dialogHelper.setMessage(R.string.dlg_remove_locales_message) + .setMessagePadding(messagePaddingLeftRight, 0, messagePaddingLeftRight, + messagePaddingBottom); } - builder.setTitle(title) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - setRemoveMode(false); - } - }) - .setPositiveButton(R.string.locale_remove_menu, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // This is a sensitive area to change. - // removeChecked() triggers a system update and "kills" the frame. - // This means that saveState + restoreState are called before - // setRemoveMode is called. - // So we want that mRemoveMode and dialog status have the right - // values - // before that save. - // We can't just call setRemoveMode(false) before calling - // removeCheched - // because that unchecks all items and removeChecked would have - // nothing - // to remove. - mRemoveMode = false; - mShowingRemoveDialog = false; - LocaleStore.LocaleInfo firstLocale = - mAdapter.getFeedItemList().get(0); - mAdapter.removeChecked(); - boolean isFirstRemoved = - firstLocale != mAdapter.getFeedItemList().get(0); - showConfirmDialog(isFirstRemoved, isFirstRemoved ? firstLocale - : mAdapter.getFeedItemList().get(0)); - setRemoveMode(false); - } + dialogHelper.setPositiveButton(R.string.locale_remove_menu, + view -> { + // This is a sensitive area to change. + // removeChecked() triggers a system update and "kills" the frame. + // This means that saveState + restoreState are called before + // setRemoveMode is called. + // So we want that mRemoveMode and dialog status have the right + // values + // before that save. + // We can't just call setRemoveMode(false) before calling + // removeCheched + // because that unchecks all items and removeChecked would have + // nothing + // to remove. + mRemoveMode = false; + mShowingRemoveDialog = false; + LocaleStore.LocaleInfo firstLocale = + mAdapter.getFeedItemList().get(0); + mAdapter.removeChecked(); + boolean isFirstRemoved = + firstLocale != mAdapter.getFeedItemList().get(0); + showConfirmDialog(isFirstRemoved, isFirstRemoved ? firstLocale + : mAdapter.getFeedItemList().get(0)); + setRemoveMode(false); + dialogHelper.getDialog().dismiss(); }) - .setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - mShowingRemoveDialog = false; - } - }) - .create() - .show(); + .setBackButton(R.string.cancel, view -> { + setRemoveMode(false); + dialogHelper.getDialog().dismiss(); + }); + dialogHelper.getDialog().setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + mShowingRemoveDialog = false; + } + }); + dialogHelper.getDialog().show(); + } + + private CustomDialogHelper createRegionDialog(Context context, String title) { + CustomDialogHelper dialogHelper = new CustomDialogHelper(context); + dialogHelper.setIcon(context.getDrawable(R.drawable.ic_settings_language_32dp)) + .setTitle(title) + .setIconPadding(0, context.getResources().getDimensionPixelSize( + R.dimen.locale_picker_dialog_icon_padding), 0, 0) + .setTitlePadding(0, context.getResources().getDimensionPixelSize( + R.dimen.locale_picker_dialog_title_padding), 0, + context.getResources().getDimensionPixelSize( + R.dimen.locale_picker_dialog_title_padding)); + return dialogHelper; } @Override @@ -483,7 +489,6 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View list.setAdapter(mAdapter); list.setOnTouchListener(this); list.requestFocus(); - mAddLanguage = layout.findViewById(R.id.add_language); mAddLanguage.setOnClickListener(new View.OnClickListener() { @Override diff --git a/tests/robotests/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java b/tests/robotests/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java index 0fc915f601f..758168268e2 100644 --- a/tests/robotests/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java @@ -26,10 +26,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; +import android.app.Dialog; import android.os.Bundle; import android.window.OnBackInvokedDispatcher; -import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; @@ -92,17 +92,17 @@ public class LocaleDialogFragmentTest { @Test public void onBackInvoked_dialogIsStillDisplaying() { mDialogFragment.setBackDispatcher(mOnBackInvokedDispatcher); - AlertDialog alertDialog = (AlertDialog) mDialogFragment.onCreateDialog(null); - alertDialog.show(); - assertThat(alertDialog).isNotNull(); - assertThat(alertDialog.isShowing()).isTrue(); + Dialog dialog = mDialogFragment.onCreateDialog(null); + dialog.show(); + assertThat(dialog).isNotNull(); + assertThat(dialog.isShowing()).isTrue(); mOnBackInvokedDispatcher.registerOnBackInvokedCallback( eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any()); mDialogFragment.getBackInvokedCallback().onBackInvoked(); - assertThat(alertDialog.isShowing()).isTrue(); + assertThat(dialog.isShowing()).isTrue(); } } diff --git a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java index 10115399cfd..0d5a952f686 100644 --- a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java +++ b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Activity; +import android.app.Dialog; import android.app.IActivityManager; import android.content.Context; import android.content.DialogInterface; @@ -53,7 +54,6 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; -import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; @@ -78,6 +78,7 @@ import org.mockito.junit.MockitoRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowDialog; import org.robolectric.shadows.ShadowLooper; import org.robolectric.util.ReflectionHelpers; @@ -87,6 +88,7 @@ import java.util.Locale; @RunWith(RobolectricTestRunner.class) @Config(shadows = { + ShadowDialog.class, ShadowAlertDialogCompat.class, ShadowActivityManager.class, com.android.settings.testutils.shadow.ShadowFragment.class, @@ -178,7 +180,7 @@ public class LocaleListEditorTest { ReflectionHelpers.setField(mLocaleListEditor, "mRemoveMode", false); ReflectionHelpers.setField(mLocaleListEditor, "mShowingRemoveDialog", false); ReflectionHelpers.setField(mLocaleListEditor, "mLocaleAdditionMode", false); - ShadowAlertDialogCompat.reset(); + ShadowDialog.reset(); } @Test @@ -209,14 +211,13 @@ public class LocaleListEditorTest { //launch dialog mLocaleListEditor.showRemoveLocaleWarningDialog(); - final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + final Dialog dialog = ShadowDialog.getLatestDialog(); assertThat(dialog).isNotNull(); - final ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog); - - assertThat(shadowDialog.getTitle()).isEqualTo( - mContext.getString(R.string.dlg_remove_locales_error_title)); + TextView dialogTitle = dialog.findViewById(R.id.dialog_with_icon_title); + assertThat(dialogTitle.getText().toString()) + .isEqualTo(mContext.getString(R.string.dlg_remove_locales_error_title)); } @Test @@ -231,14 +232,13 @@ public class LocaleListEditorTest { //launch dialog mLocaleListEditor.showRemoveLocaleWarningDialog(); - final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + final Dialog dialog = ShadowDialog.getLatestDialog(); assertThat(dialog).isNotNull(); - final ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog); - - assertThat(shadowDialog.getMessage()).isEqualTo( - mContext.getString(R.string.dlg_remove_locales_message)); + TextView dialogMessage = dialog.findViewById(R.id.dialog_with_icon_message); + assertThat(dialogMessage.getText().toString()) + .isEqualTo(mContext.getString(R.string.dlg_remove_locales_message)); } @Test @@ -253,13 +253,12 @@ public class LocaleListEditorTest { //launch dialog mLocaleListEditor.showRemoveLocaleWarningDialog(); - final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + final Dialog dialog = ShadowDialog.getLatestDialog(); assertThat(dialog).isNotNull(); - final ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog); - - assertThat(shadowDialog.getMessage()).isNull(); + TextView dialogMessage = dialog.findViewById(R.id.dialog_with_icon_message); + assertThat(dialogMessage.getText().isEmpty()).isTrue(); } @Test @@ -280,12 +279,12 @@ public class LocaleListEditorTest { //launch the first dialog mLocaleListEditor.showRemoveLocaleWarningDialog(); - final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + final Dialog dialog = ShadowDialog.getLatestDialog(); assertThat(dialog).isNotNull(); // click the remove button - dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); + dialog.findViewById(R.id.button_ok).performClick(); ShadowLooper.idleMainLooper(); assertThat(dialog.isShowing()).isFalse(); diff --git a/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java b/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java index 824954da52d..9415d00e98b 100644 --- a/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java +++ b/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java @@ -35,6 +35,7 @@ import androidx.test.annotation.UiThreadTest; import androidx.test.core.app.ApplicationProvider; import com.android.internal.app.LocaleStore; +import com.android.settings.R; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.ResourcesUtils; @@ -82,11 +83,9 @@ public class LocaleDialogFragmentTest { LocaleDialogFragment.LocaleDialogController.DialogContent dialogContent = controller.getDialogContent(); - assertEquals(ResourcesUtils.getResourcesString( - mContext, "button_label_confirmation_of_system_locale_change"), + assertEquals(R.string.button_label_confirmation_of_system_locale_change, dialogContent.mPositiveButton); - assertEquals(ResourcesUtils.getResourcesString(mContext, "cancel"), - dialogContent.mNegativeButton); + assertEquals(R.string.cancel, dialogContent.mNegativeButton); } @Test @@ -99,9 +98,8 @@ public class LocaleDialogFragmentTest { LocaleDialogFragment.LocaleDialogController.DialogContent dialogContent = controller.getDialogContent(); - assertEquals(ResourcesUtils.getResourcesString(mContext, "okay"), - dialogContent.mPositiveButton); - assertTrue(dialogContent.mNegativeButton.isEmpty()); + assertEquals(R.string.okay, dialogContent.mPositiveButton); + assertTrue(dialogContent.mNegativeButton == 0); } @Test From 32f7568cd480426b46971282e2da3984be738f85 Mon Sep 17 00:00:00 2001 From: Ben Lin Date: Wed, 12 Feb 2025 04:15:46 +0000 Subject: [PATCH 09/20] Rename DesktopMode API back Also fix some indentation. Bug: 395826065 Test: atest, Manual Flag: EXEMPT bugfix Change-Id: I50d30a8508d387e36981fb3bdd13b99480baedcf --- .../development/DesktopExperiencePreferenceController.java | 2 +- .../development/DesktopExperiencePreferenceControllerTest.java | 3 ++- .../development/DesktopModePreferenceControllerTest.java | 3 ++- .../DesktopModeSecondaryDisplayPreferenceControllerTest.java | 3 ++- .../development/FreeformWindowsPreferenceControllerTest.java | 1 + 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/development/DesktopExperiencePreferenceController.java b/src/com/android/settings/development/DesktopExperiencePreferenceController.java index f6173a974e7..28e92fcdac5 100644 --- a/src/com/android/settings/development/DesktopExperiencePreferenceController.java +++ b/src/com/android/settings/development/DesktopExperiencePreferenceController.java @@ -98,7 +98,7 @@ public class DesktopExperiencePreferenceController extends DeveloperOptionsPrefe @Override public CharSequence getSummary() { - if (DesktopModeStatus.isInternalDisplayEligibleToHostDesktops(mContext) + if (DesktopModeStatus.isDeviceEligibleForDesktopMode(mContext) && !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue()) { return mContext.getString( R.string.enable_desktop_experience_features_summary_with_desktop); diff --git a/tests/robotests/src/com/android/settings/development/DesktopExperiencePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/DesktopExperiencePreferenceControllerTest.java index 6296626dedb..388e9b21f29 100644 --- a/tests/robotests/src/com/android/settings/development/DesktopExperiencePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/DesktopExperiencePreferenceControllerTest.java @@ -102,7 +102,8 @@ public class DesktopExperiencePreferenceControllerTest { // Set desktop mode available when(mResources.getBoolean(com.android.internal.R.bool.config_isDesktopModeSupported)) .thenReturn(true); - when(mResources.getBoolean(com.android.internal.R.bool.config_canInternalDisplayHostDesktops)) + when(mResources + .getBoolean(com.android.internal.R.bool.config_canInternalDisplayHostDesktops)) .thenReturn(true); ShadowSystemProperties.override("persist.wm.debug.desktop_mode_enforce_device_restrictions", "false"); diff --git a/tests/robotests/src/com/android/settings/development/DesktopModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/DesktopModePreferenceControllerTest.java index 02aa8725bd0..9f718f9db62 100644 --- a/tests/robotests/src/com/android/settings/development/DesktopModePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/DesktopModePreferenceControllerTest.java @@ -104,7 +104,8 @@ public class DesktopModePreferenceControllerTest { // Set desktop mode available when(mResources.getBoolean(R.bool.config_isDesktopModeSupported)) .thenReturn(true); - when(mResources.getBoolean(com.android.internal.R.bool.config_canInternalDisplayHostDesktops)) + when(mResources + .getBoolean(com.android.internal.R.bool.config_canInternalDisplayHostDesktops)) .thenReturn(true); ShadowSystemProperties.override("persist.wm.debug.desktop_mode_enforce_device_restrictions", "false"); diff --git a/tests/robotests/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceControllerTest.java index f3b96a4425f..2284d92cd70 100644 --- a/tests/robotests/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/DesktopModeSecondaryDisplayPreferenceControllerTest.java @@ -97,7 +97,8 @@ public class DesktopModeSecondaryDisplayPreferenceControllerTest { when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); mController.displayPreference(mScreen); when(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(false); - when(mResources.getBoolean(com.android.internal.R.bool.config_canInternalDisplayHostDesktops)) + when(mResources + .getBoolean(com.android.internal.R.bool.config_canInternalDisplayHostDesktops)) .thenReturn(false); } diff --git a/tests/robotests/src/com/android/settings/development/FreeformWindowsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/FreeformWindowsPreferenceControllerTest.java index cf32ae83e00..b4b0bccf832 100644 --- a/tests/robotests/src/com/android/settings/development/FreeformWindowsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/FreeformWindowsPreferenceControllerTest.java @@ -91,6 +91,7 @@ public class FreeformWindowsPreferenceControllerTest { doReturn(mFragmentManager).when(mActivity).getSupportFragmentManager(); doReturn(mActivity).when(mFragment).getActivity(); doReturn(true).when(mResources).getBoolean(R.bool.config_isDesktopModeSupported); + doReturn(true).when(mResources).getBoolean(R.bool.config_canInternalDisplayHostDesktops); mController = new FreeformWindowsPreferenceController(mContext, mFragment); when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); when(mContext.getPackageManager()).thenReturn(mPackageManager); From 4251ebeccac90a3d797604d0fe486371c5a53302 Mon Sep 17 00:00:00 2001 From: Alice Kuo Date: Wed, 12 Feb 2025 09:17:27 +0800 Subject: [PATCH 10/20] Do not disable LE audio offload as the developer option being disabled The logic wrong to check the a2dp offload disable status to disable LE audio offload together as the user turn off the developer option. Change to use the support status, and reset the state to align the support status. Bug: 395725497 Fix: 395725497 Flag: EXEMPT, trivial bug fix Test: Manual turn off developer option to check the status change Test: atest SettingsRoboTests:com.android.settings.development.BluetoothLeAudioHwOffloadPreferenceControllerTest Change-Id: I59d3e378495c90f007b46f05b74fdaf19d1520ce --- ...toothLeAudioHwOffloadPreferenceController.java | 14 ++++++++------ ...hLeAudioHwOffloadPreferenceControllerTest.java | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceController.java index 1890fbd9818..dfa808f644d 100644 --- a/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceController.java +++ b/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceController.java @@ -16,6 +16,7 @@ package com.android.settings.development; +import static com.android.settings.development.BluetoothA2dpHwOffloadPreferenceController.A2DP_OFFLOAD_SUPPORTED_PROPERTY; import static com.android.settings.development.BluetoothA2dpHwOffloadPreferenceController.A2DP_OFFLOAD_DISABLED_PROPERTY; import android.bluetooth.BluetoothAdapter; @@ -105,13 +106,14 @@ public class BluetoothLeAudioHwOffloadPreferenceController (mBluetoothAdapter.isLeAudioSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED); final boolean leAudioOffloadSupported = SystemProperties.getBoolean(LE_AUDIO_OFFLOAD_SUPPORTED_PROPERTY, false); - final boolean a2dpOffloadDisabled = - SystemProperties.getBoolean(A2DP_OFFLOAD_DISABLED_PROPERTY, false); - if (leAudioEnabled && leAudioOffloadSupported && !a2dpOffloadDisabled) { - ((TwoStatePreference) mPreference).setChecked(true); - SystemProperties.set(LE_AUDIO_OFFLOAD_DISABLED_PROPERTY, "true"); - } else { + final boolean a2dpOffloadSupported = + SystemProperties.getBoolean(A2DP_OFFLOAD_SUPPORTED_PROPERTY, false); + + if(!leAudioEnabled || !leAudioOffloadSupported || !a2dpOffloadSupported) { mPreference.setEnabled(false); + } else { + ((TwoStatePreference) mPreference).setChecked(false); + SystemProperties.set(LE_AUDIO_OFFLOAD_DISABLED_PROPERTY, "false"); } } diff --git a/tests/robotests/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceControllerTest.java index aa05f628f7f..c2a4d033080 100644 --- a/tests/robotests/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioHwOffloadPreferenceControllerTest.java @@ -20,6 +20,8 @@ import static android.bluetooth.BluetoothStatusCodes.FEATURE_SUPPORTED; import static com.android.settings.development.BluetoothA2dpHwOffloadPreferenceController .A2DP_OFFLOAD_DISABLED_PROPERTY; +import static com.android.settings.development.BluetoothA2dpHwOffloadPreferenceController + .A2DP_OFFLOAD_SUPPORTED_PROPERTY; import static com.android.settings.development.BluetoothLeAudioHwOffloadPreferenceController .LE_AUDIO_OFFLOAD_DISABLED_PROPERTY; import static com.android.settings.development.BluetoothLeAudioHwOffloadPreferenceController @@ -120,4 +122,17 @@ public class BluetoothLeAudioHwOffloadPreferenceControllerTest { leAueioDisabled = SystemProperties.getBoolean(LE_AUDIO_OFFLOAD_DISABLED_PROPERTY, false); assertThat(leAueioDisabled).isTrue(); } + + @Test + public void asDisableDeveloperOption_ResetLEOffloadBasedOnA2dpLeAudioOffloadSupported() { + SystemProperties.set(LE_AUDIO_OFFLOAD_SUPPORTED_PROPERTY, Boolean.toString(true)); + SystemProperties.set(A2DP_OFFLOAD_SUPPORTED_PROPERTY, Boolean.toString(true)); + + SystemProperties.set( + LE_AUDIO_OFFLOAD_DISABLED_PROPERTY, Boolean.toString(true)); + mController.onDeveloperOptionsSwitchDisabled(); + boolean leAueioDisabled = + SystemProperties.getBoolean(LE_AUDIO_OFFLOAD_DISABLED_PROPERTY, false); + assertThat(leAueioDisabled).isFalse(); + } } From 997cd29458e229b703d4e68113968396aa5384ff Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Wed, 22 Jan 2025 08:39:00 +0000 Subject: [PATCH 11/20] [Settings] Add a flag to control show new System language picker UI or not Bug: 394542699 Test: manual Test: atest SettingsRoboTests:com.android.settings.localepicker.LocaleListEditorTest Flag: EXEMPT refactor Change-Id: I694e92cb7f61d208c36cb4b40aa3182cd838d5bb --- res/layout/locale_order_list.xml | 3 +- res/xml/languages.xml | 7 ++++ .../localepicker/LocaleListEditor.java | 38 ++++++++++++------- ...alePickerBaseListPreferenceController.java | 9 ++--- .../localepicker/LocaleListEditorTest.java | 5 +++ 5 files changed, 43 insertions(+), 19 deletions(-) diff --git a/res/layout/locale_order_list.xml b/res/layout/locale_order_list.xml index da1eb62d996..36bb06d060b 100644 --- a/res/layout/locale_order_list.xml +++ b/res/layout/locale_order_list.xml @@ -44,7 +44,8 @@ android:textAlignment="textStart" android:text="@string/add_a_language" style="@style/Base.Widget.AppCompat.Button.Borderless" - android:textAppearance="?android:attr/textAppearanceListItem"/> + android:textAppearance="?android:attr/textAppearanceListItem" + android:visibility="gone"/> diff --git a/res/xml/languages.xml b/res/xml/languages.xml index d478aa18ded..1fb6368efba 100644 --- a/res/xml/languages.xml +++ b/res/xml/languages.xml @@ -31,6 +31,13 @@ + + (mLocaleList.size()); mPreferences = new ArrayMap<>(); mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); } @@ -246,7 +243,7 @@ public abstract class LocalePickerBaseListPreferenceController extends private void setupLocaleList() { mLocaleList = getLocaleCollectorController(mContext).getSupportedLocaleList( mParentLocale, false, mIsCountryMode); - mLocaleOptions.clear(); + mLocaleOptions = new ArrayList<>(mLocaleList.size()); } private List getSortedLocaleList( @@ -262,7 +259,9 @@ public abstract class LocalePickerBaseListPreferenceController extends boolean shouldShowLocaleEditor = shouldShowLocaleEditor(localeInfo); if (shouldShowLocaleEditor) { List feedItemList = getUserLocaleList(); - feedItemList.add(localeInfo); + for (LocaleStore.LocaleInfo locale : mLocaleList) { + feedItemList.add(locale); + } LocaleList localeList = new LocaleList(feedItemList.stream() .map(LocaleStore.LocaleInfo::getLocale) .toArray(Locale[]::new)); diff --git a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java index 0d5a952f686..4272afe8eba 100644 --- a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java +++ b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java @@ -57,6 +57,7 @@ import android.widget.TextView; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; +import androidx.preference.Preference; import androidx.test.core.app.ApplicationProvider; import com.android.internal.app.LocaleStore; @@ -145,6 +146,8 @@ public class LocaleListEditorTest { private ImageView mDragHandle; @Mock private NotificationController mNotificationController; + @Mock + private Preference mAddLanguagePreference; @Rule public final CheckFlagsRule mCheckFlagsRule = @@ -168,6 +171,8 @@ public class LocaleListEditorTest { context.getSystemService(Context.USER_SERVICE)); ReflectionHelpers.setField(mLocaleListEditor, "mAdapter", mAdapter); ReflectionHelpers.setField(mLocaleListEditor, "mAddLanguage", mAddLanguage); + ReflectionHelpers.setField(mLocaleListEditor, "mAddLanguagePreference", + mAddLanguagePreference); ReflectionHelpers.setField(mLocaleListEditor, "mFragmentManager", mFragmentManager); ReflectionHelpers.setField(mLocaleListEditor, "mMetricsFeatureProvider", mMetricsFeatureProvider); From b3938a0244548272f504a6fd5693cdd6595bd182 Mon Sep 17 00:00:00 2001 From: Weng Su Date: Thu, 13 Feb 2025 10:48:25 +0800 Subject: [PATCH 12/20] Fix accessibility issues in Private DNS Settings - Keep the Save button enabled at all times - Show error in the Hostname view to remind the user - "The field is required" error - "The hostname you typed isn't valid" error Bug: 386323822 Flag: EXEMPT bugfix Test: Manual testing atest -c PrivateDnsModeDialogPreferenceTest \ PrivateDnsPreferenceControllerTest Change-Id: I63973bd5001b838d7f27827e6a6d4ac96ac78ca9 --- res/layout/private_dns_mode_dialog.xml | 29 +++-- res/values/strings.xml | 6 + .../PrivateDnsModeDialogPreference.java | 116 ++++++++++-------- .../PrivateDnsPreferenceController.java | 2 +- .../PrivateDnsModeDialogPreferenceTest.java | 105 +++++++++------- 5 files changed, 151 insertions(+), 107 deletions(-) diff --git a/res/layout/private_dns_mode_dialog.xml b/res/layout/private_dns_mode_dialog.xml index 96ebd2c3955..bee949a906e 100644 --- a/res/layout/private_dns_mode_dialog.xml +++ b/res/layout/private_dns_mode_dialog.xml @@ -16,8 +16,10 @@ + android:layout_height="wrap_content" + android:theme="@style/Theme.AppCompat.DayNight"> - + android:hint="@string/private_dns_title" + android:theme="@style/Theme.Settings" + app:endIconMode="clear_text" + app:errorEnabled="true"> + + + Emergency address Used as your location when you make an emergency call over Wi\u2011Fi + + Hostname + + The field is required + + The hostname you typed isn\u2019t valid Learn more about Private DNS features diff --git a/src/com/android/settings/network/PrivateDnsModeDialogPreference.java b/src/com/android/settings/network/PrivateDnsModeDialogPreference.java index 3b99777720a..fc517ac4495 100644 --- a/src/com/android/settings/network/PrivateDnsModeDialogPreference.java +++ b/src/com/android/settings/network/PrivateDnsModeDialogPreference.java @@ -23,14 +23,12 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.app.settings.SettingsEnums; import android.content.ActivityNotFoundException; -import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.ConnectivitySettingsManager; import android.os.UserHandle; import android.os.UserManager; -import android.provider.Settings; import android.text.Editable; import android.text.TextWatcher; import android.text.method.LinkMovementMethod; @@ -55,6 +53,7 @@ import com.android.settingslib.HelpUtils; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; +import com.google.android.material.textfield.TextInputLayout; import com.google.common.net.InternetDomainName; import java.util.HashMap; @@ -64,7 +63,7 @@ import java.util.Map; * Dialog to set the Private DNS */ public class PrivateDnsModeDialogPreference extends CustomDialogPreferenceCompat implements - DialogInterface.OnClickListener, RadioGroup.OnCheckedChangeListener, TextWatcher { + RadioGroup.OnCheckedChangeListener, TextWatcher { public static final String ANNOTATION_URL = "url"; @@ -80,16 +79,9 @@ public class PrivateDnsModeDialogPreference extends CustomDialogPreferenceCompat } @VisibleForTesting - static final String MODE_KEY = Settings.Global.PRIVATE_DNS_MODE; + TextInputLayout mHostnameLayout; @VisibleForTesting - static final String HOSTNAME_KEY = Settings.Global.PRIVATE_DNS_SPECIFIER; - - public static String getHostnameFromSettings(ContentResolver cr) { - return Settings.Global.getString(cr, HOSTNAME_KEY); - } - - @VisibleForTesting - EditText mEditText; + EditText mHostnameText; @VisibleForTesting RadioGroup mRadioGroup; @VisibleForTesting @@ -136,22 +128,17 @@ public class PrivateDnsModeDialogPreference extends CustomDialogPreferenceCompat // by the controller. holder.itemView.setEnabled(true); } + + setSaveButtonListener(); } @Override protected void onBindDialogView(View view) { final Context context = getContext(); - final ContentResolver contentResolver = context.getContentResolver(); - mMode = ConnectivitySettingsManager.getPrivateDnsMode(context); - - mEditText = view.findViewById(R.id.private_dns_mode_provider_hostname); - mEditText.addTextChangedListener(this); - mEditText.setText(getHostnameFromSettings(contentResolver)); - mRadioGroup = view.findViewById(R.id.private_dns_radio_group); - mRadioGroup.setOnCheckedChangeListener(this); mRadioGroup.check(PRIVATE_DNS_MAP.getOrDefault(mMode, R.id.private_dns_mode_opportunistic)); + mRadioGroup.setOnCheckedChangeListener(this); // Initial radio button text final RadioButton offRadioButton = view.findViewById(R.id.private_dns_mode_off); @@ -163,6 +150,13 @@ public class PrivateDnsModeDialogPreference extends CustomDialogPreferenceCompat final RadioButton providerRadioButton = view.findViewById(R.id.private_dns_mode_provider); providerRadioButton.setText(com.android.settingslib.R.string.private_dns_mode_provider); + mHostnameLayout = view.findViewById(R.id.private_dns_mode_provider_hostname_layout); + mHostnameText = view.findViewById(R.id.private_dns_mode_provider_hostname); + if (mHostnameText != null) { + mHostnameText.setText(ConnectivitySettingsManager.getPrivateDnsHostname(context)); + mHostnameText.addTextChangedListener(this); + } + final TextView helpTextView = view.findViewById(R.id.private_dns_help_info); helpTextView.setMovementMethod(LinkMovementMethod.getInstance()); final Intent helpIntent = HelpUtils.getHelpIntent(context, @@ -176,22 +170,8 @@ public class PrivateDnsModeDialogPreference extends CustomDialogPreferenceCompat } else { helpTextView.setText(""); } - } - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == DialogInterface.BUTTON_POSITIVE) { - final Context context = getContext(); - if (mMode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) { - // Only clickable if hostname is valid, so we could save it safely - ConnectivitySettingsManager.setPrivateDnsHostname(context, - mEditText.getText().toString()); - } - - FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().action(context, - SettingsEnums.ACTION_PRIVATE_DNS_MODE, mMode); - ConnectivitySettingsManager.setPrivateDnsMode(context, mMode); - } + updateDialogInfo(); } @Override @@ -241,24 +221,58 @@ public class PrivateDnsModeDialogPreference extends CustomDialogPreferenceCompat return getEnforcedAdmin() != null; } - private Button getSaveButton() { - final AlertDialog dialog = (AlertDialog) getDialog(); - if (dialog == null) { - return null; - } - return dialog.getButton(DialogInterface.BUTTON_POSITIVE); - } - private void updateDialogInfo() { final boolean modeProvider = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME == mMode; - if (mEditText != null) { - mEditText.setEnabled(modeProvider); - } - final Button saveButton = getSaveButton(); - if (saveButton != null) { - saveButton.setEnabled(modeProvider - ? InternetDomainName.isValid(mEditText.getText().toString()) - : true); + if (mHostnameLayout != null) { + mHostnameLayout.setEnabled(modeProvider); + mHostnameLayout.setErrorEnabled(false); } } + + private void setSaveButtonListener() { + View.OnClickListener onClickListener = v -> doSaveButton(); + DialogInterface.OnShowListener onShowListener = dialog -> { + if (dialog == null) { + Log.e(TAG, "The DialogInterface is null!"); + return; + } + Button saveButton = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE); + if (saveButton == null) { + Log.e(TAG, "Can't get the save button!"); + return; + } + saveButton.setOnClickListener(onClickListener); + }; + setOnShowListener(onShowListener); + } + + @VisibleForTesting + void doSaveButton() { + Context context = getContext(); + if (mMode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) { + if (mHostnameLayout == null || mHostnameText == null) { + Log.e(TAG, "Can't find hostname resources!"); + return; + } + if (mHostnameText.getText().isEmpty()) { + mHostnameLayout.setError(context.getString(R.string.private_dns_field_require)); + Log.w(TAG, "The hostname is empty!"); + return; + } + if (!InternetDomainName.isValid(mHostnameText.getText().toString())) { + mHostnameLayout.setError(context.getString(R.string.private_dns_hostname_invalid)); + Log.w(TAG, "The hostname is invalid!"); + return; + } + + ConnectivitySettingsManager.setPrivateDnsHostname(context, + mHostnameText.getText().toString()); + } + + ConnectivitySettingsManager.setPrivateDnsMode(context, mMode); + + FeatureFactory.getFeatureFactory().getMetricsFeatureProvider() + .action(context, SettingsEnums.ACTION_PRIVATE_DNS_MODE, mMode); + getDialog().dismiss(); + } } diff --git a/src/com/android/settings/network/PrivateDnsPreferenceController.java b/src/com/android/settings/network/PrivateDnsPreferenceController.java index 21e4926f490..291ce3307fd 100644 --- a/src/com/android/settings/network/PrivateDnsPreferenceController.java +++ b/src/com/android/settings/network/PrivateDnsPreferenceController.java @@ -135,7 +135,7 @@ public class PrivateDnsPreferenceController extends BasePreferenceController com.android.settingslib.R.string.private_dns_mode_opportunistic); case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME: return dnsesResolved - ? PrivateDnsModeDialogPreference.getHostnameFromSettings(cr) + ? ConnectivitySettingsManager.getPrivateDnsHostname(mContext) : res.getString( com.android.settingslib.R.string.private_dns_mode_provider_failure); } diff --git a/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogPreferenceTest.java b/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogPreferenceTest.java index 458000292df..c1811915a90 100644 --- a/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogPreferenceTest.java @@ -21,14 +21,12 @@ import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNI import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; -import android.content.DialogInterface; import android.net.ConnectivitySettingsManager; import android.view.LayoutInflater; import android.view.View; @@ -88,31 +86,31 @@ public class PrivateDnsModeDialogPreferenceTest { } @Test - public void testOnCheckedChanged_dnsModeOff_disableEditText() { + public void onCheckedChanged_dnsModeOff_disableHostnameText() { mPreference.onCheckedChanged(null, R.id.private_dns_mode_off); assertThat(mPreference.mMode).isEqualTo(PRIVATE_DNS_MODE_OFF); - assertThat(mPreference.mEditText.isEnabled()).isFalse(); + assertThat(mPreference.mHostnameText.isEnabled()).isFalse(); } @Test - public void testOnCheckedChanged_dnsModeOpportunistic_disableEditText() { + public void onCheckedChanged_dnsModeOpportunistic_disableHostnameText() { mPreference.onCheckedChanged(null, R.id.private_dns_mode_opportunistic); assertThat(mPreference.mMode).isEqualTo(PRIVATE_DNS_MODE_OPPORTUNISTIC); - assertThat(mPreference.mEditText.isEnabled()).isFalse(); + assertThat(mPreference.mHostnameText.isEnabled()).isFalse(); } @Test - public void testOnCheckedChanged_dnsModeProvider_enableEditText() { + public void onCheckedChanged_dnsModeProvider_enableHostnameText() { mPreference.onCheckedChanged(null, R.id.private_dns_mode_provider); assertThat(mPreference.mMode).isEqualTo(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); - assertThat(mPreference.mEditText.isEnabled()).isTrue(); + assertThat(mPreference.mHostnameText.isEnabled()).isTrue(); } @Test - public void testOnBindDialogView_containsCorrectData() { + public void onBindDialogView_containsCorrectData() { // Don't set settings to the default value ("opportunistic") as that // risks masking failure to read the mode from settings. ConnectivitySettingsManager.setPrivateDnsMode(mContext, PRIVATE_DNS_MODE_OFF); @@ -123,57 +121,74 @@ public class PrivateDnsModeDialogPreferenceTest { new LinearLayout(mContext), false); mPreference.onBindDialogView(view); - assertThat(mPreference.mEditText.getText().toString()).isEqualTo(HOST_NAME); + assertThat(mPreference.mHostnameText.getText().toString()).isEqualTo(HOST_NAME); assertThat(mPreference.mRadioGroup.getCheckedRadioButtonId()).isEqualTo( R.id.private_dns_mode_off); } @Test - public void testOnCheckedChanged_switchMode_saveButtonHasCorrectState() { - final String[] INVALID_HOST_NAMES = new String[] { - INVALID_HOST_NAME, - "2001:db8::53", // IPv6 string literal - "192.168.1.1", // IPv4 string literal - }; + public void doSaveButton_changeToOffMode_saveData() { + // Set the default settings to OPPORTUNISTIC + ConnectivitySettingsManager.setPrivateDnsMode(mContext, PRIVATE_DNS_MODE_OPPORTUNISTIC); - for (String invalid : INVALID_HOST_NAMES) { - // Set invalid hostname - mPreference.mEditText.setText(invalid); - - mPreference.onCheckedChanged(null, R.id.private_dns_mode_off); - assertWithMessage("off: " + invalid).that(mSaveButton.isEnabled()).isTrue(); - - mPreference.onCheckedChanged(null, R.id.private_dns_mode_opportunistic); - assertWithMessage("opportunistic: " + invalid).that(mSaveButton.isEnabled()).isTrue(); - - mPreference.onCheckedChanged(null, R.id.private_dns_mode_provider); - assertWithMessage("provider: " + invalid).that(mSaveButton.isEnabled()).isFalse(); - } - } - - @Test - public void testOnClick_positiveButtonClicked_saveData() { - // Set the default settings to OFF - ConnectivitySettingsManager.setPrivateDnsMode(mContext, PRIVATE_DNS_MODE_OFF); - - mPreference.mMode = PRIVATE_DNS_MODE_OPPORTUNISTIC; - mPreference.onClick(null, DialogInterface.BUTTON_POSITIVE); + mPreference.mMode = PRIVATE_DNS_MODE_OFF; + mPreference.doSaveButton(); // Change to OPPORTUNISTIC - assertThat(ConnectivitySettingsManager.getPrivateDnsMode(mContext)).isEqualTo( - PRIVATE_DNS_MODE_OPPORTUNISTIC); + assertThat(ConnectivitySettingsManager.getPrivateDnsMode(mContext)) + .isEqualTo(PRIVATE_DNS_MODE_OFF); } @Test - public void testOnClick_negativeButtonClicked_doNothing() { + public void doSaveButton_changeToOpportunisticMode_saveData() { // Set the default settings to OFF ConnectivitySettingsManager.setPrivateDnsMode(mContext, PRIVATE_DNS_MODE_OFF); mPreference.mMode = PRIVATE_DNS_MODE_OPPORTUNISTIC; - mPreference.onClick(null, DialogInterface.BUTTON_NEGATIVE); + mPreference.doSaveButton(); - // Still equal to OFF - assertThat(ConnectivitySettingsManager.getPrivateDnsMode(mContext)).isEqualTo( - PRIVATE_DNS_MODE_OFF); + // Change to OPPORTUNISTIC + assertThat(ConnectivitySettingsManager.getPrivateDnsMode(mContext)) + .isEqualTo(PRIVATE_DNS_MODE_OPPORTUNISTIC); + } + + @Test + public void doSaveButton_changeToProviderHostnameMode_saveData() { + // Set the default settings to OFF + ConnectivitySettingsManager.setPrivateDnsMode(mContext, PRIVATE_DNS_MODE_OFF); + + mPreference.onCheckedChanged(null, R.id.private_dns_mode_provider); + mPreference.mHostnameText.setText(HOST_NAME); + mPreference.doSaveButton(); + + // Change to PROVIDER_HOSTNAME + assertThat(ConnectivitySettingsManager.getPrivateDnsMode(mContext)) + .isEqualTo(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); + assertThat(ConnectivitySettingsManager.getPrivateDnsHostname(mContext)) + .isEqualTo(HOST_NAME); + } + + @Test + public void doSaveButton_providerHostnameIsEmpty_setHostnameError() { + ConnectivitySettingsManager.setPrivateDnsMode(mContext, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); + ConnectivitySettingsManager.setPrivateDnsHostname(mContext, HOST_NAME); + mPreference.onCheckedChanged(null, R.id.private_dns_mode_provider); + + mPreference.mHostnameText.setText(""); + mPreference.doSaveButton(); + + assertThat(mPreference.mHostnameLayout.isErrorEnabled()).isTrue(); + } + + @Test + public void doSaveButton_providerHostnameIsInvalid_setHostnameError() { + ConnectivitySettingsManager.setPrivateDnsMode(mContext, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); + ConnectivitySettingsManager.setPrivateDnsHostname(mContext, HOST_NAME); + mPreference.onCheckedChanged(null, R.id.private_dns_mode_provider); + + mPreference.mHostnameText.setText(INVALID_HOST_NAME); + mPreference.doSaveButton(); + + assertThat(mPreference.mHostnameLayout.isErrorEnabled()).isTrue(); } } From 31be904d9b3ef3def0e88fc6217a2889d8ea47c4 Mon Sep 17 00:00:00 2001 From: Jason Chang Date: Thu, 13 Feb 2025 04:34:50 +0000 Subject: [PATCH 13/20] Fix Talkback should not announce disruptive announcement "Re-enter your PIN" page. Remove the calling announceForAccessibility() to prevent disruptive announcement. Flag: EXEMPT bugfix Bug: 383259346 Bug: 384608181 Test: local build and verify Talkback announcement in "Re-enter your PIN" page. Change-Id: Ide29dde477a095db12eb959dc3d89b6689bd6ab2 --- src/com/android/settings/password/ChooseLockPassword.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index 80f73b95c66..626518d0b81 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -770,7 +770,7 @@ public class ChooseLockPassword extends SettingsActivity { // If the stage changed, announce the header for accessibility. This // is a no-op when accessibility is disabled. if (previousStage != stage) { - mLayout.announceForAccessibility(mLayout.getHeaderText()); + getActivity().setTitle(mLayout.getHeaderText()); } } From a2bdafaf5edcea159e07808a8f9162992b50d1ac Mon Sep 17 00:00:00 2001 From: mxyyiyi Date: Fri, 7 Feb 2025 14:43:15 +0800 Subject: [PATCH 14/20] [Expressive Battery] Refactor pref with anomaly hint. - WarningFramePreference & warning_frame_preference.xml is a battery customized layout which looks like a standard Preference layout with an optional app icon & warning chip. - PowerGaugePreference extends the WarningFramePreference to display the app Preference with optional warning chip in Battery > Battery Usage. - PowerUsageTimePreference extends the WarningFramePreference to display the usage time with optional warning chip in Battery > Battery Usage > App battery usage. Bug: 349652542 Test: atest BatteryUsageBreakdownControllerTest PowerGaugePreferenceTest PowerUsageTimeControllerTest Flag: com.android.settingslib.widget.theme.flags.is_expressive_design_enabled Change-Id: I5d22703ccc487c54a2bbbc1d9737b92a2de54ba5 --- res/layout/power_usage_time.xml | 49 ------------ ...rence.xml => warning_frame_preference.xml} | 9 +-- .../fuelgauge/PowerUsageTimeController.java | 6 +- .../fuelgauge/PowerUsageTimePreference.java | 65 +-------------- .../fuelgauge/WarningFramePreference.java | 79 +++++++++++++++++++ .../AnomalyAppItemPreference.java | 61 -------------- .../BatteryUsageBreakdownController.java | 8 +- .../batteryusage/PowerGaugePreference.java | 23 +----- .../PowerUsageTimeControllerTest.java | 50 ++++++------ .../BatteryUsageBreakdownControllerTest.java | 26 +++--- .../PowerGaugePreferenceTest.java | 62 ++++++++++----- 11 files changed, 178 insertions(+), 260 deletions(-) delete mode 100644 res/layout/power_usage_time.xml rename res/layout/{anomaly_app_item_preference.xml => warning_frame_preference.xml} (87%) create mode 100644 src/com/android/settings/fuelgauge/WarningFramePreference.java delete mode 100644 src/com/android/settings/fuelgauge/batteryusage/AnomalyAppItemPreference.java diff --git a/res/layout/power_usage_time.xml b/res/layout/power_usage_time.xml deleted file mode 100644 index bad68f4517f..00000000000 --- a/res/layout/power_usage_time.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/anomaly_app_item_preference.xml b/res/layout/warning_frame_preference.xml similarity index 87% rename from res/layout/anomaly_app_item_preference.xml rename to res/layout/warning_frame_preference.xml index 681fe68339a..4e624ce1efc 100644 --- a/res/layout/anomaly_app_item_preference.xml +++ b/res/layout/warning_frame_preference.xml @@ -22,7 +22,7 @@ - - - + \ No newline at end of file diff --git a/src/com/android/settings/fuelgauge/PowerUsageTimeController.java b/src/com/android/settings/fuelgauge/PowerUsageTimeController.java index 0dfdd5d3238..e01a95c99a6 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageTimeController.java +++ b/src/com/android/settings/fuelgauge/PowerUsageTimeController.java @@ -93,10 +93,10 @@ public class PowerUsageTimeController extends BasePreferenceController { || (summaryTimeMs == 0 && !TextUtils.equals(anomalyHintKey, preference.getKey()))) { return false; } - preference.setTimeTitle(mContext.getString(titleResId)); - preference.setTimeSummary(getPowerUsageTimeInfo(summaryTimeMs)); + preference.setTitle(mContext.getString(titleResId)); + preference.setSummary(getPowerUsageTimeInfo(summaryTimeMs)); if (TextUtils.equals(anomalyHintKey, preference.getKey())) { - preference.setAnomalyHint(anomalyHintText); + preference.setHint(anomalyHintText); } preference.setVisible(true); return true; diff --git a/src/com/android/settings/fuelgauge/PowerUsageTimePreference.java b/src/com/android/settings/fuelgauge/PowerUsageTimePreference.java index 16c7770181b..d2f39ff5f19 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageTimePreference.java +++ b/src/com/android/settings/fuelgauge/PowerUsageTimePreference.java @@ -17,74 +17,17 @@ package com.android.settings.fuelgauge; import android.content.Context; -import android.text.TextUtils; import android.util.AttributeSet; -import android.view.View; -import android.widget.TextView; -import androidx.annotation.VisibleForTesting; -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; - -import com.android.settings.R; +import com.android.settingslib.widget.GroupSectionDividerMixin; /** Custom preference for displaying the app power usage time. */ -public class PowerUsageTimePreference extends Preference { +public class PowerUsageTimePreference extends WarningFramePreference implements + GroupSectionDividerMixin { private static final String TAG = "PowerUsageTimePreference"; - @VisibleForTesting CharSequence mTimeTitle; - @VisibleForTesting CharSequence mTimeSummary; - @VisibleForTesting CharSequence mAnomalyHintText; - public PowerUsageTimePreference(Context context, AttributeSet attrs) { super(context, attrs); - setLayoutResource(R.layout.power_usage_time); - } - - void setTimeTitle(CharSequence timeTitle) { - if (!TextUtils.equals(mTimeTitle, timeTitle)) { - mTimeTitle = timeTitle; - notifyChanged(); - } - } - - void setTimeSummary(CharSequence timeSummary) { - if (!TextUtils.equals(mTimeSummary, timeSummary)) { - mTimeSummary = timeSummary; - notifyChanged(); - } - } - - void setAnomalyHint(CharSequence anomalyHintText) { - if (!TextUtils.equals(mAnomalyHintText, anomalyHintText)) { - mAnomalyHintText = anomalyHintText; - notifyChanged(); - } - } - - private void showAnomalyHint(PreferenceViewHolder view) { - if (TextUtils.isEmpty(mAnomalyHintText)) { - return; - } - final View anomalyHintView = view.findViewById(R.id.anomaly_hints); - if (anomalyHintView == null) { - return; - } - final TextView warningInfo = anomalyHintView.findViewById(R.id.warning_info); - if (warningInfo == null) { - return; - } - warningInfo.setText(mAnomalyHintText); - anomalyHintView.setVisibility(View.VISIBLE); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder view) { - super.onBindViewHolder(view); - - ((TextView) view.findViewById(R.id.time_title)).setText(mTimeTitle); - ((TextView) view.findViewById(R.id.time_summary)).setText(mTimeSummary); - - showAnomalyHint(view); + setSelectable(false); } } diff --git a/src/com/android/settings/fuelgauge/WarningFramePreference.java b/src/com/android/settings/fuelgauge/WarningFramePreference.java new file mode 100644 index 00000000000..ba8685a4ccf --- /dev/null +++ b/src/com/android/settings/fuelgauge/WarningFramePreference.java @@ -0,0 +1,79 @@ +/* + * 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.fuelgauge; + +import android.annotation.Nullable; +import android.content.Context; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.Space; +import android.widget.TextView; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.R; +import com.android.settingslib.Utils; + +/** + * Custom preference for displaying the {@link Preference} with an optional hint chip. + */ +public class WarningFramePreference extends Preference { + private final int mTitleColorNormal; + private final int mSummaryColorNormal; + + @Nullable private CharSequence mHintText; + + public WarningFramePreference(Context context, AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.warning_frame_preference); + mTitleColorNormal = + Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary); + mSummaryColorNormal = + Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary); + } + + /** Sets the text of hint to show. */ + public void setHint(@Nullable CharSequence hintText) { + if (!TextUtils.equals(mHintText, hintText)) { + mHintText = hintText; + notifyChanged(); + } + } + + @Override + public void onBindViewHolder(PreferenceViewHolder view) { + super.onBindViewHolder(view); + + final LinearLayout warningChipFrame = + (LinearLayout) view.findViewById(R.id.warning_chip_frame); + final Space warningPaddingPlaceHolder = + warningChipFrame.findViewById(R.id.warning_padding_placeholder); + warningPaddingPlaceHolder.setVisibility(getIcon() != null ? View.VISIBLE : View.GONE); + if (!TextUtils.isEmpty(mHintText)) { + ((TextView) warningChipFrame.findViewById(R.id.warning_info)).setText(mHintText); + warningChipFrame.setVisibility(View.VISIBLE); + } else { + warningChipFrame.setVisibility(View.GONE); + } + ((TextView) view.findViewById(android.R.id.title)).setTextColor(mTitleColorNormal); + ((TextView) view.findViewById(android.R.id.summary)).setTextColor(mSummaryColorNormal); + } +} + diff --git a/src/com/android/settings/fuelgauge/batteryusage/AnomalyAppItemPreference.java b/src/com/android/settings/fuelgauge/batteryusage/AnomalyAppItemPreference.java deleted file mode 100644 index 592d30833ed..00000000000 --- a/src/com/android/settings/fuelgauge/batteryusage/AnomalyAppItemPreference.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.fuelgauge.batteryusage; - -import android.annotation.Nullable; -import android.content.Context; -import android.text.TextUtils; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.preference.PreferenceViewHolder; - -import com.android.settings.R; - -class AnomalyAppItemPreference extends PowerGaugePreference { - - private static final String TAG = "AnomalyAppItemPreference"; - - private CharSequence mAnomalyHintText; - - AnomalyAppItemPreference(Context context) { - super(context, /* attrs */ null); - setLayoutResource(R.layout.anomaly_app_item_preference); - } - - void setAnomalyHint(@Nullable CharSequence anomalyHintText) { - if (!TextUtils.equals(mAnomalyHintText, anomalyHintText)) { - mAnomalyHintText = anomalyHintText; - notifyChanged(); - } - } - - @Override - public void onBindViewHolder(PreferenceViewHolder viewHolder) { - super.onBindViewHolder(viewHolder); - final LinearLayout warningChipView = - (LinearLayout) viewHolder.findViewById(R.id.warning_chip); - - if (!TextUtils.isEmpty(mAnomalyHintText)) { - ((TextView) warningChipView.findViewById(R.id.warning_info)).setText(mAnomalyHintText); - warningChipView.setVisibility(View.VISIBLE); - } else { - warningChipView.setVisibility(View.GONE); - } - } -} diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java index 6fd4eb5449f..280fa185e0c 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java @@ -381,15 +381,15 @@ public class BatteryUsageBreakdownController extends BasePreferenceController continue; } final String prefKey = entry.getKey(); - AnomalyAppItemPreference preference = mRootPreferenceGroup.findPreference(prefKey); + PowerGaugePreference preference = mRootPreferenceGroup.findPreference(prefKey); if (preference != null) { isAdded = true; } else { - preference = (AnomalyAppItemPreference) mPreferenceCache.get(prefKey); + preference = (PowerGaugePreference) mPreferenceCache.get(prefKey); } // Creates new instance if cached preference is not found. if (preference == null) { - preference = new AnomalyAppItemPreference(mPrefContext); + preference = new PowerGaugePreference(mPrefContext); preference.setKey(prefKey); mPreferenceCache.put(prefKey, preference); } @@ -398,7 +398,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController preference.setOrder(++preferenceOrder); preference.setSingleLineTitle(true); // Updates App item preference style - preference.setAnomalyHint(isAnomalyBatteryDiffEntry(entry) ? mAnomalyHintString : null); + preference.setHint(isAnomalyBatteryDiffEntry(entry) ? mAnomalyHintString : null); // Sets the BatteryDiffEntry to preference for launching detailed page. preference.setBatteryDiffEntry(entry); preference.setSelectable(entry.validForRestriction()); diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerGaugePreference.java b/src/com/android/settings/fuelgauge/batteryusage/PowerGaugePreference.java index 1fc9abd4c5e..866b9ae6e05 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PowerGaugePreference.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PowerGaugePreference.java @@ -28,7 +28,7 @@ import androidx.preference.PreferenceViewHolder; import com.android.settings.R; import com.android.settings.Utils; -import com.android.settingslib.widget.AppPreference; +import com.android.settings.fuelgauge.WarningFramePreference; /** * Custom preference for displaying battery usage info as a bar and an icon on the left for the @@ -37,7 +37,7 @@ import com.android.settingslib.widget.AppPreference; *

The battery usage info could be usage percentage or usage time. The preference won't show any * icon if it is null. */ -public class PowerGaugePreference extends AppPreference { +public class PowerGaugePreference extends WarningFramePreference { // Please see go/battery-usage-app-list-alpha private static final float SELECTABLE_ALPHA = 1f; @@ -51,7 +51,6 @@ public class PowerGaugePreference extends AppPreference { private CharSequence mContentDescription; private CharSequence mProgress; private CharSequence mProgressContentDescription; - private boolean mShowAnomalyIcon; public PowerGaugePreference( Context context, Drawable icon, CharSequence contentDescription, BatteryEntry info) { @@ -79,7 +78,6 @@ public class PowerGaugePreference extends AppPreference { setWidgetLayoutResource(R.layout.preference_widget_summary); mInfo = info; mContentDescription = contentDescription; - mShowAnomalyIcon = false; mTitleColorNormal = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary); } @@ -108,17 +106,6 @@ public class PowerGaugePreference extends AppPreference { return mProgress.toString(); } - /** Sets whether to show anomaly icon */ - public void shouldShowAnomalyIcon(boolean showAnomalyIcon) { - mShowAnomalyIcon = showAnomalyIcon; - notifyChanged(); - } - - /** Gets whether to show anomaly icon */ - public boolean showAnomalyIcon() { - return mShowAnomalyIcon; - } - public void setBatteryDiffEntry(BatteryDiffEntry entry) { mBatteryDiffEntry = entry; } @@ -149,12 +136,6 @@ public class PowerGaugePreference extends AppPreference { if (!TextUtils.isEmpty(mProgressContentDescription)) { subtitle.setContentDescription(mProgressContentDescription); } - if (mShowAnomalyIcon) { - subtitle.setCompoundDrawablesRelativeWithIntrinsicBounds( - R.drawable.ic_warning_24dp, 0, 0, 0); - } else { - subtitle.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0); - } if (mContentDescription != null) { final TextView titleView = (TextView) view.findViewById(android.R.id.title); titleView.setContentDescription(mContentDescription); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageTimeControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageTimeControllerTest.java index e8eb1260bcd..60ece9df424 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageTimeControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageTimeControllerTest.java @@ -99,9 +99,9 @@ public final class PowerUsageTimeControllerTest { /* anomalyHintText= */ null); verifyOnePreferenceInvisible(mBackgroundTimePreference); - verify(mScreenTimePreference).setTimeTitle("Screen time"); - verify(mScreenTimePreference).setTimeSummary("1 min"); - verify(mScreenTimePreference, never()).setAnomalyHint(anyString()); + verify(mScreenTimePreference).setTitle("Screen time"); + verify(mScreenTimePreference).setSummary("1 min"); + verify(mScreenTimePreference, never()).setHint(anyString()); } @Test @@ -117,9 +117,9 @@ public final class PowerUsageTimeControllerTest { /* anomalyHintText= */ null); verifyOnePreferenceInvisible(mScreenTimePreference); - verify(mBackgroundTimePreference).setTimeTitle("Background time"); - verify(mBackgroundTimePreference).setTimeSummary("2 min"); - verify(mBackgroundTimePreference, never()).setAnomalyHint(anyString()); + verify(mBackgroundTimePreference).setTitle("Background time"); + verify(mBackgroundTimePreference).setSummary("2 min"); + verify(mBackgroundTimePreference, never()).setHint(anyString()); } @Test @@ -135,12 +135,12 @@ public final class PowerUsageTimeControllerTest { /* anomalyHintText= */ null); verifyAllPreferencesVisible(true); - verify(mScreenTimePreference).setTimeTitle("Screen time"); - verify(mScreenTimePreference).setTimeSummary("1 min"); - verify(mScreenTimePreference, never()).setAnomalyHint(anyString()); - verify(mBackgroundTimePreference).setTimeTitle("Background time"); - verify(mBackgroundTimePreference).setTimeSummary("2 min"); - verify(mBackgroundTimePreference, never()).setAnomalyHint(anyString()); + verify(mScreenTimePreference).setTitle("Screen time"); + verify(mScreenTimePreference).setSummary("1 min"); + verify(mScreenTimePreference, never()).setHint(anyString()); + verify(mBackgroundTimePreference).setTitle("Background time"); + verify(mBackgroundTimePreference).setSummary("2 min"); + verify(mBackgroundTimePreference, never()).setHint(anyString()); verify(mPowerUsageTimeCategory).setTitle("App usage for 12 am-2 am"); } @@ -173,8 +173,8 @@ public final class PowerUsageTimeControllerTest { /* anomalyHintText= */ null); verifyAllPreferencesVisible(true); - verify(mScreenTimePreference).setTimeSummary("1 min"); - verify(mBackgroundTimePreference).setTimeSummary("Less than a minute"); + verify(mScreenTimePreference).setSummary("1 min"); + verify(mBackgroundTimePreference).setSummary("Less than a minute"); } @Test @@ -190,8 +190,8 @@ public final class PowerUsageTimeControllerTest { /* anomalyHintText= */ null); verifyAllPreferencesVisible(true); - verify(mScreenTimePreference).setTimeSummary("Less than a minute"); - verify(mBackgroundTimePreference).setTimeSummary("2 min"); + verify(mScreenTimePreference).setSummary("Less than a minute"); + verify(mBackgroundTimePreference).setSummary("2 min"); } @Test @@ -207,8 +207,8 @@ public final class PowerUsageTimeControllerTest { /* anomalyHintText= */ null); verifyAllPreferencesVisible(true); - verify(mScreenTimePreference).setTimeSummary("Less than a minute"); - verify(mBackgroundTimePreference).setTimeSummary("Less than a minute"); + verify(mScreenTimePreference).setSummary("Less than a minute"); + verify(mBackgroundTimePreference).setSummary("Less than a minute"); } @Test @@ -224,8 +224,8 @@ public final class PowerUsageTimeControllerTest { TEST_ANOMALY_HINT_TEXT); verifyAllPreferencesVisible(true); - verify(mScreenTimePreference).setAnomalyHint(TEST_ANOMALY_HINT_TEXT); - verify(mBackgroundTimePreference, never()).setAnomalyHint(anyString()); + verify(mScreenTimePreference).setHint(TEST_ANOMALY_HINT_TEXT); + verify(mBackgroundTimePreference, never()).setHint(anyString()); } @Test @@ -241,8 +241,8 @@ public final class PowerUsageTimeControllerTest { TEST_ANOMALY_HINT_TEXT); verifyAllPreferencesVisible(true); - verify(mScreenTimePreference, never()).setAnomalyHint(anyString()); - verify(mBackgroundTimePreference).setAnomalyHint(TEST_ANOMALY_HINT_TEXT); + verify(mScreenTimePreference, never()).setHint(anyString()); + verify(mBackgroundTimePreference).setHint(TEST_ANOMALY_HINT_TEXT); } @Test @@ -258,9 +258,9 @@ public final class PowerUsageTimeControllerTest { TEST_ANOMALY_HINT_TEXT); verifyAllPreferencesVisible(true); - verify(mScreenTimePreference).setTimeSummary("Less than a minute"); - verify(mScreenTimePreference).setAnomalyHint(TEST_ANOMALY_HINT_TEXT); - verify(mBackgroundTimePreference, never()).setAnomalyHint(anyString()); + verify(mScreenTimePreference).setSummary("Less than a minute"); + verify(mScreenTimePreference).setHint(TEST_ANOMALY_HINT_TEXT); + verify(mBackgroundTimePreference, never()).setHint(anyString()); } private void verifySetPrefToVisible(Preference pref, boolean isVisible) { diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java index c4cbb988ae6..7ad7c491239 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java @@ -67,7 +67,7 @@ public final class BatteryUsageBreakdownControllerTest { @Mock private PreferenceGroup mRootPreferenceGroup; @Mock private Drawable mDrawable; @Mock private BatteryHistEntry mBatteryHistEntry; - @Mock private AnomalyAppItemPreference mAnomalyAppItemPreference; + @Mock private PowerGaugePreference mPowerGaugePreference; private Context mContext; private FakeFeatureFactory mFeatureFactory; @@ -131,13 +131,13 @@ public final class BatteryUsageBreakdownControllerTest { BatteryDiffEntry.sResourceCache.put( "fakeBatteryDiffEntryKey", new BatteryEntry.NameAndIcon("fakeName", /* icon= */ null, /* iconId= */ 1)); - doReturn(mAnomalyAppItemPreference).when(mRootPreferenceGroup).findPreference(PREF_KEY); + doReturn(mPowerGaugePreference).when(mRootPreferenceGroup).findPreference(PREF_KEY); } @Test public void onDestroy_clearPreferenceCacheAndPreferenceGroupRemoveAll() { // Ensures the testing environment is correct. - mBatteryUsageBreakdownController.mPreferenceCache.put(PREF_KEY, mAnomalyAppItemPreference); + mBatteryUsageBreakdownController.mPreferenceCache.put(PREF_KEY, mPowerGaugePreference); assertThat(mBatteryUsageBreakdownController.mPreferenceCache).hasSize(1); mBatteryUsageBreakdownController.onDestroy(); @@ -204,25 +204,25 @@ public final class BatteryUsageBreakdownControllerTest { @Test public void removeAndCacheAllUnusedPreferences_removePref_buildCacheAndRemoveAllPreference() { doReturn(1).when(mRootPreferenceGroup).getPreferenceCount(); - doReturn(mAnomalyAppItemPreference).when(mRootPreferenceGroup).getPreference(0); + doReturn(mPowerGaugePreference).when(mRootPreferenceGroup).getPreference(0); doReturn(PREF_KEY2).when(mBatteryHistEntry).getKey(); - doReturn(PREF_KEY).when(mAnomalyAppItemPreference).getKey(); + doReturn(PREF_KEY).when(mPowerGaugePreference).getKey(); // Ensures the testing data is correct. assertThat(mBatteryUsageBreakdownController.mPreferenceCache).isEmpty(); mBatteryUsageBreakdownController.removeAndCacheAllUnusedPreferences(); assertThat(mBatteryUsageBreakdownController.mPreferenceCache.get(PREF_KEY)) - .isEqualTo(mAnomalyAppItemPreference); - verify(mRootPreferenceGroup).removePreference(mAnomalyAppItemPreference); + .isEqualTo(mPowerGaugePreference); + verify(mRootPreferenceGroup).removePreference(mPowerGaugePreference); } @Test public void removeAndCacheAllUnusedPreferences_keepPref_KeepAllPreference() { doReturn(1).when(mRootPreferenceGroup).getPreferenceCount(); - doReturn(mAnomalyAppItemPreference).when(mRootPreferenceGroup).getPreference(0); + doReturn(mPowerGaugePreference).when(mRootPreferenceGroup).getPreference(0); doReturn(PREF_KEY).when(mBatteryDiffEntry).getKey(); - doReturn(PREF_KEY).when(mAnomalyAppItemPreference).getKey(); + doReturn(PREF_KEY).when(mPowerGaugePreference).getKey(); // Ensures the testing data is correct. assertThat(mBatteryUsageBreakdownController.mPreferenceCache).isEmpty(); @@ -246,11 +246,11 @@ public final class BatteryUsageBreakdownControllerTest { @Test public void handlePreferenceTreeClick_forAppEntry_returnTrue() { mBatteryDiffEntry.mConsumerType = ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY; - doReturn(mBatteryDiffEntry).when(mAnomalyAppItemPreference).getBatteryDiffEntry(); + doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry(); assertThat( mBatteryUsageBreakdownController.handlePreferenceTreeClick( - mAnomalyAppItemPreference)) + mPowerGaugePreference)) .isTrue(); verify(mMetricsFeatureProvider) .action( @@ -264,11 +264,11 @@ public final class BatteryUsageBreakdownControllerTest { @Test public void handlePreferenceTreeClick_forSystemEntry_returnTrue() { mBatteryDiffEntry.mConsumerType = ConvertUtils.CONSUMER_TYPE_UID_BATTERY; - doReturn(mBatteryDiffEntry).when(mAnomalyAppItemPreference).getBatteryDiffEntry(); + doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry(); assertThat( mBatteryUsageBreakdownController.handlePreferenceTreeClick( - mAnomalyAppItemPreference)) + mPowerGaugePreference)) .isTrue(); verify(mMetricsFeatureProvider) .action( diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerGaugePreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerGaugePreferenceTest.java index f64ef495aeb..0bad0a0943c 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerGaugePreferenceTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerGaugePreferenceTest.java @@ -19,19 +19,20 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.graphics.drawable.Drawable; -import android.graphics.drawable.VectorDrawable; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; -import android.widget.TextView; +import android.widget.Space; import androidx.preference.PreferenceViewHolder; import com.android.settings.R; +import com.android.settingslib.widget.SettingsThemeHelper; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @@ -48,17 +49,17 @@ public class PowerGaugePreferenceTest { private View mWidgetView; private PreferenceViewHolder mPreferenceViewHolder; + @Mock Drawable mMockIcon; + @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; + mPowerGaugePreference = new PowerGaugePreference(mContext); mRootView = LayoutInflater.from(mContext) - .inflate( - com.android.settingslib.widget.preference.app.R.layout - .preference_app, - null); + .inflate(mPowerGaugePreference.getLayoutResource(), null); mWidgetView = LayoutInflater.from(mContext).inflate(R.layout.preference_widget_summary, null); final LinearLayout widgetFrame = mRootView.findViewById(android.R.id.widget_frame); @@ -66,31 +67,56 @@ public class PowerGaugePreferenceTest { widgetFrame.addView(mWidgetView); mPreferenceViewHolder = PreferenceViewHolder.createInstanceForTests(mRootView); - mPowerGaugePreference = new PowerGaugePreference(mContext); assertThat(mPowerGaugePreference.getLayoutResource()) - .isEqualTo(com.android.settingslib.widget.preference.app.R.layout.preference_app); + .isEqualTo( + SettingsThemeHelper.isExpressiveTheme(mContext) + ? R.layout.expressive_warning_frame_preference + : R.layout.warning_frame_preference); } @Test - public void testOnBindViewHolder_showAnomaly_bindAnomalyIcon() { - mPowerGaugePreference.shouldShowAnomalyIcon(true); + public void testOnBindViewHolder_showHint_hasHintChip() { + mPowerGaugePreference.setHint("Hint Text"); + mPowerGaugePreference.setIcon(mMockIcon); mPowerGaugePreference.onBindViewHolder(mPreferenceViewHolder); - TextView widgetSummary = (TextView) mPreferenceViewHolder.findViewById(R.id.widget_summary); - final Drawable[] drawables = widgetSummary.getCompoundDrawablesRelative(); + final LinearLayout warningChipFrame = + (LinearLayout) mPreferenceViewHolder.findViewById(R.id.warning_chip_frame); + final Space warningPaddingPlaceHolder = + warningChipFrame.findViewById(R.id.warning_padding_placeholder); - assertThat(drawables[0]).isInstanceOf(VectorDrawable.class); + assertThat(warningChipFrame.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(warningPaddingPlaceHolder.getVisibility()).isEqualTo(View.VISIBLE); } @Test - public void testOnBindViewHolder_notShowAnomaly_bindAnomalyIcon() { - mPowerGaugePreference.shouldShowAnomalyIcon(false); + public void testOnBindViewHolder_emptyHintText_withoutHintChip() { + mPowerGaugePreference.setHint(""); + mPowerGaugePreference.setIcon(mMockIcon); mPowerGaugePreference.onBindViewHolder(mPreferenceViewHolder); - TextView widgetSummary = (TextView) mPreferenceViewHolder.findViewById(R.id.widget_summary); - final Drawable[] drawables = widgetSummary.getCompoundDrawablesRelative(); + final LinearLayout warningChipFrame = + (LinearLayout) mPreferenceViewHolder.findViewById(R.id.warning_chip_frame); + final Space warningPaddingPlaceholder = + warningChipFrame.findViewById(R.id.warning_padding_placeholder); - assertThat(drawables[0]).isNull(); + assertThat(warningChipFrame.getVisibility()).isEqualTo(View.GONE); + assertThat(warningPaddingPlaceholder.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void testOnBindViewHolder_noAppIconWithHintText_hasChipWithoutPaddingPlaceholder() { + mPowerGaugePreference.setHint("Anomaly Hint Text"); + mPowerGaugePreference.setIcon(null); + mPowerGaugePreference.onBindViewHolder(mPreferenceViewHolder); + + final LinearLayout warningChipFrame = + (LinearLayout) mPreferenceViewHolder.findViewById(R.id.warning_chip_frame); + final Space warningPaddingPlaceHolder = + warningChipFrame.findViewById(R.id.warning_padding_placeholder); + + assertThat(warningChipFrame.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(warningPaddingPlaceHolder.getVisibility()).isEqualTo(View.GONE); } @Test From 4f9e8cb7517e3b83d469d6be74c91aab1e2f3f9b Mon Sep 17 00:00:00 2001 From: mxyyiyi Date: Mon, 10 Feb 2025 16:05:25 +0800 Subject: [PATCH 15/20] [Expressive Battery] Implement the expressive style for anomaly hint chip For expressive style: - Update the padding start & end of pref with anomaly hint - Add the new background res to update anomaly hint style to outlined - Reduce the vertical space between pref text and anomaly hint. Bug: 349652542 Test: visual Flag: com.android.settingslib.widget.theme.flags.is_expressive_design_enabled Change-Id: Iec9240d2edba5042c5a1f740b662db75bea41d0e --- .../expressive_battery_hints_chip_bg.xml | 22 ++++++++++ ...xpressive_battery_hints_chip_bg_ripple.xml | 21 +++++++++ .../expressive_warning_frame_preference.xml | 43 +++++++++++++++++++ res/layout/power_anomaly_hints.xml | 5 +-- .../fuelgauge/WarningFramePreference.java | 34 +++++++++++---- 5 files changed, 114 insertions(+), 11 deletions(-) create mode 100644 res/drawable-v35/expressive_battery_hints_chip_bg.xml create mode 100644 res/drawable-v35/expressive_battery_hints_chip_bg_ripple.xml create mode 100644 res/layout-v35/expressive_warning_frame_preference.xml diff --git a/res/drawable-v35/expressive_battery_hints_chip_bg.xml b/res/drawable-v35/expressive_battery_hints_chip_bg.xml new file mode 100644 index 00000000000..86e9f6a699c --- /dev/null +++ b/res/drawable-v35/expressive_battery_hints_chip_bg.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/res/drawable-v35/expressive_battery_hints_chip_bg_ripple.xml b/res/drawable-v35/expressive_battery_hints_chip_bg_ripple.xml new file mode 100644 index 00000000000..542a40c6ffb --- /dev/null +++ b/res/drawable-v35/expressive_battery_hints_chip_bg_ripple.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/res/layout-v35/expressive_warning_frame_preference.xml b/res/layout-v35/expressive_warning_frame_preference.xml new file mode 100644 index 00000000000..ba110443146 --- /dev/null +++ b/res/layout-v35/expressive_warning_frame_preference.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/power_anomaly_hints.xml b/res/layout/power_anomaly_hints.xml index 7ee419e4dcb..9498523d670 100644 --- a/res/layout/power_anomaly_hints.xml +++ b/res/layout/power_anomaly_hints.xml @@ -17,13 +17,12 @@ + android:id="@+id/warning_chip" + android:padding="8dp"> Date: Tue, 11 Feb 2025 21:09:48 +0000 Subject: [PATCH 16/20] Show per-displays pref groups after built-in When the built-in display settings as well as CD settings are both present in the fragment, show external display settings category after built-in. This applies to the per-display fragment and the initial display list fragment. Stop showing per-display settings nested in a parent list, as this was causing extra spacing and complicating the code. Flag: com.android.settings.flags.display_topology_pane_in_display_list Test: ExternalDisplayPreferenceFragmentTest Test: manually check topology mode for single display, multiple displays, in both fragments Test: manually check v1 UI for single display, multiple displays, in both fragments Bug: b/352648432 Bug: b/396116157 Change-Id: I7fdf72d198988feb1e7559f96a54f7680cf5b8a6 --- .../ExternalDisplayPreferenceFragment.java | 82 ++++++++++++------- ...ExternalDisplayPreferenceFragmentTest.java | 76 +++++++++++------ .../display/ExternalDisplayTestBase.java | 7 +- 3 files changed, 110 insertions(+), 55 deletions(-) diff --git a/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java b/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java index af03bab7579..5de96b33c80 100644 --- a/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java +++ b/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java @@ -59,6 +59,7 @@ import com.android.settingslib.widget.TwoTargetPreference; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.function.Consumer; /** * The Settings screen for External Displays configuration and connection management. @@ -85,8 +86,6 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen BUILTIN_DISPLAY_LIST(70, "builtin_display_list_preference", R.string.builtin_display_settings_category), - DISPLAYS_LIST(80, "displays_list_preference", null), - // If shown, footer should appear below everything. FOOTER(90, "footer_preference", null); @@ -333,15 +332,6 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen return args != null ? args.getInt(DISPLAY_ID_ARG, INVALID_DISPLAY) : INVALID_DISPLAY; } - @NonNull - private PreferenceCategory getDisplaysListPreference(@NonNull Context context) { - if (mDisplaysPreference == null) { - mDisplaysPreference = new PreferenceCategory(context); - PrefBasics.DISPLAYS_LIST.apply(mDisplaysPreference); - } - return mDisplaysPreference; - } - @NonNull private PreferenceCategory getBuiltinDisplayListPreference(@NonNull Context context) { if (mBuiltinDisplayPreference == null) { @@ -455,6 +445,26 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen EXTERNAL_DISPLAY_NOT_FOUND_FOOTER_RESOURCE)); } + private static PreferenceCategory getCategoryForDisplay(@NonNull Display display, + @NonNull PrefRefresh screen, @NonNull Context context) { + // The rest of the settings are in a category with the display name as the title. + String categoryKey = "expanded_display_items_" + display.getDisplayId(); + var category = (PreferenceCategory) screen.findUnusedPreference(categoryKey); + + if (category != null) { + screen.addPreference(category); + } else { + category = new PreferenceCategory(context); + screen.addPreference(category); + category.setPersistent(false); + category.setKey(categoryKey); + category.setTitle(display.getName()); + category.setOrder(PrefBasics.BUILTIN_DISPLAY_LIST.order + 1); + } + + return category; + } + private void showDisplaySettings(@NonNull Display display, @NonNull PrefRefresh screen, @NonNull Context context) { final var isEnabled = mInjector != null && mInjector.isDisplayEnabled(display); @@ -469,8 +479,18 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen if (!isTopologyPaneEnabled(mInjector)) { screen.addPreference(updateIllustrationImage(context, displayRotation)); } - screen.addPreference(updateResolutionPreference(context, display)); - screen.addPreference(updateRotationPreference(context, display, displayRotation)); + + Consumer adder; + if (isTopologyPaneEnabled(mInjector)) { + adder = getCategoryForDisplay(display, screen, context)::addPreference; + // The category may have already been populated if it was retrieved from the PrefRefresh + // backup, but we still need to update resolution and rotation items. + } else { + adder = screen::addPreference; + } + + adder.accept(updateResolutionPreference(context, display)); + adder.accept(updateRotationPreference(context, display, displayRotation)); if (isResolutionSettingEnabled(mInjector)) { // Do not show the footer about changing resolution affecting apps. This is not in the // UX design for v2, and there is no good place to put it, since (a) if it is on the @@ -483,12 +503,12 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen // TODO(b/352648432): probably remove footer once the pane and rest of v2 UI is in // place. if (!isTopologyPaneEnabled(mInjector)) { - screen.addPreference(updateFooterPreference(context, + adder.accept(updateFooterPreference(context, EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE)); } } if (isDisplaySizeSettingEnabled(mInjector)) { - screen.addPreference(updateSizePreference(context)); + adder.accept(updateSizePreference(context)); } } @@ -508,23 +528,28 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen private void showDisplaysList(@NonNull List displaysToShow, @NonNull PrefRefresh screen, @NonNull Context context) { maybeAddV2Components(context, screen); - var displayGroupPref = getDisplaysListPreference(context); - if (!displaysToShow.isEmpty()) { - screen.addPreference(displayGroupPref); - } - try (var groupCleanable = new PrefRefresh(displayGroupPref)) { - for (var display : displaysToShow) { - var pref = getDisplayPreference(context, display, groupCleanable); - pref.setSummary(display.getMode().getPhysicalWidth() + " x " - + display.getMode().getPhysicalHeight()); - } + int order = PrefBasics.BUILTIN_DISPLAY_LIST.order; + for (var display : displaysToShow) { + var pref = getDisplayPreference(context, display, screen, ++order); + pref.setSummary(display.getMode().getPhysicalWidth() + " x " + + display.getMode().getPhysicalHeight()); } } + @VisibleForTesting + static String displayListDisplayCategoryKey(int displayId) { + return "display_list_display_category_" + displayId; + } + + @VisibleForTesting + static String resolutionRotationPreferenceKey(int displayId) { + return "display_id_" + displayId; + } + private Preference getDisplayPreference(@NonNull Context context, - @NonNull Display display, @NonNull PrefRefresh groupCleanable) { - var itemKey = "display_id_" + display.getDisplayId(); - var categoryKey = itemKey + "_category"; + @NonNull Display display, @NonNull PrefRefresh groupCleanable, int categoryOrder) { + var itemKey = resolutionRotationPreferenceKey(display.getDisplayId()); + var categoryKey = displayListDisplayCategoryKey(display.getDisplayId()); var category = (PreferenceCategory) groupCleanable.findUnusedPreference(categoryKey); if (category != null) { @@ -534,6 +559,7 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen category = new PreferenceCategory(context); category.setPersistent(false); category.setKey(categoryKey); + category.setOrder(categoryOrder); // Must add the category to the hierarchy before adding its descendants. Otherwise // the category will not have a preference manager, which causes an exception when a // child is added to it. diff --git a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java b/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java index 12af7726a42..623b20947cf 100644 --- a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java +++ b/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java @@ -22,6 +22,8 @@ import static com.android.settings.connecteddevice.display.ExternalDisplayPrefer import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_SETTINGS_RESOURCE; import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_SIZE_SUMMARY_RESOURCE; import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.PREVIOUSLY_SHOWN_LIST_KEY; +import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.displayListDisplayCategoryKey; +import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.resolutionRotationPreferenceKey; import static com.android.settings.flags.Flags.FLAG_DISPLAY_SIZE_CONNECTED_DISPLAY_SETTING; import static com.android.settings.flags.Flags.FLAG_DISPLAY_TOPOLOGY_PANE_IN_DISPLAY_LIST; import static com.android.settingslib.widget.FooterPreference.KEY_FOOTER; @@ -79,6 +81,19 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa assertThat(mPreferenceIdFromResource).isEqualTo(EXTERNAL_DISPLAY_SETTINGS_RESOURCE); } + private void assertDisplayList(boolean present, int displayId) { + // In display list fragment, there is a combined resolution/rotation preference key. + var category = mPreferenceScreen.findPreference(displayListDisplayCategoryKey(displayId)); + var pref = mPreferenceScreen.findPreference(resolutionRotationPreferenceKey(displayId)); + if (present) { + assertThat(category).isNotNull(); + assertThat(pref).isNotNull(); + } else { + assertThat(category).isNull(); + assertThat(pref).isNull(); + } + } + @Test @UiThreadTest public void testShowDisplayList() { @@ -89,19 +104,26 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa fragment.onSaveInstanceStateCallback(outState); assertThat(outState.getBoolean(PREVIOUSLY_SHOWN_LIST_KEY)).isFalse(); assertThat(mHandler.getPendingMessages().size()).isEqualTo(1); - PreferenceCategory pref = mPreferenceScreen.findPreference(PrefBasics.DISPLAYS_LIST.key); - assertThat(pref).isNull(); + + // Combined resolution/refresh rate are not available in displays list because the pane is + // disabled (v1 UI). + assertDisplayList(false, EXTERNAL_DISPLAY_ID); + assertDisplayList(false, OVERLAY_DISPLAY_ID); + // Individual resolution preference is not available in displays list. + assertThat(mPreferenceScreen.findPreference( + PrefBasics.EXTERNAL_DISPLAY_RESOLUTION.key)) + .isNull(); + verify(mMockedInjector, never()).getAllDisplays(); mHandler.flush(); assertThat(mHandler.getPendingMessages().size()).isEqualTo(0); verify(mMockedInjector).getAllDisplays(); - pref = mPreferenceScreen.findPreference(PrefBasics.DISPLAYS_LIST.key); - assertThat(pref).isNotNull(); - assertThat(pref.getPreferenceCount()).isEqualTo(2); + assertDisplayList(true, EXTERNAL_DISPLAY_ID); + assertDisplayList(true, OVERLAY_DISPLAY_ID); fragment.onSaveInstanceStateCallback(outState); assertThat(outState.getBoolean(PREVIOUSLY_SHOWN_LIST_KEY)).isTrue(); - pref = mPreferenceScreen.findPreference(PrefBasics.DISPLAY_TOPOLOGY.key); + Preference pref = mPreferenceScreen.findPreference(PrefBasics.DISPLAY_TOPOLOGY.key); assertThat(pref).isNull(); pref = mPreferenceScreen.findPreference(PrefBasics.BUILTIN_DISPLAY_LIST.key); @@ -122,8 +144,7 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa pref = mPreferenceScreen.findPreference(PrefBasics.MIRROR.key); assertThat(pref).isNotNull(); - pref = mPreferenceScreen.findPreference(PrefBasics.DISPLAYS_LIST.key); - assertThat(pref).isNull(); + assertDisplayList(false, mDisplays[1].getDisplayId()); PreferenceCategory listPref = mPreferenceScreen.findPreference(PrefBasics.BUILTIN_DISPLAY_LIST.key); @@ -148,11 +169,10 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa pref = mPreferenceScreen.findPreference(PrefBasics.MIRROR.key); assertThat(pref).isNull(); - PreferenceCategory listPref = - mPreferenceScreen.findPreference(PrefBasics.DISPLAYS_LIST.key); - assertThat(listPref).isNull(); + assertDisplayList(false, EXTERNAL_DISPLAY_ID); + assertDisplayList(false, OVERLAY_DISPLAY_ID); - listPref = mPreferenceScreen.findPreference(PrefBasics.BUILTIN_DISPLAY_LIST.key); + var listPref = mPreferenceScreen.findPreference(PrefBasics.BUILTIN_DISPLAY_LIST.key); assertThat(listPref).isNull(); } @@ -161,19 +181,23 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa public void testLaunchDisplaySettingFromList() { initFragment(); mHandler.flush(); - PreferenceCategory pref = mPreferenceScreen.findPreference(PrefBasics.DISPLAYS_LIST.key); - assertThat(pref).isNotNull(); - var display1Category = (PreferenceCategory) pref.getPreference(0); + assertDisplayList(true, EXTERNAL_DISPLAY_ID); + assertDisplayList(true, OVERLAY_DISPLAY_ID); + PreferenceCategory display1Category = mPreferenceScreen.findPreference( + displayListDisplayCategoryKey(EXTERNAL_DISPLAY_ID)); var display1Pref = (DisplayPreference) display1Category.getPreference(0); - var display2Category = (PreferenceCategory) pref.getPreference(1); + PreferenceCategory display2Category = mPreferenceScreen.findPreference( + displayListDisplayCategoryKey(OVERLAY_DISPLAY_ID)); var display2Pref = (DisplayPreference) display2Category.getPreference(0); - assertThat(display1Pref.getKey()).isEqualTo("display_id_" + 1); + assertThat(display1Pref.getKey()).isEqualTo( + resolutionRotationPreferenceKey(EXTERNAL_DISPLAY_ID)); assertThat("" + display1Category.getTitle()).isEqualTo("HDMI"); assertThat("" + display1Pref.getSummary()).isEqualTo("1920 x 1080"); display1Pref.onPreferenceClick(display1Pref); assertThat(mDisplayIdArg).isEqualTo(1); verify(mMockedMetricsLogger).writePreferenceClickMetric(display1Pref); - assertThat(display2Pref.getKey()).isEqualTo("display_id_" + 2); + assertThat(display2Pref.getKey()).isEqualTo( + resolutionRotationPreferenceKey(OVERLAY_DISPLAY_ID)); assertThat("" + display2Category.getTitle()).isEqualTo("Overlay #1"); assertThat("" + display2Pref.getSummary()).isEqualTo("1240 x 780"); display2Pref.onPreferenceClick(display2Pref); @@ -190,9 +214,12 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa // Only one display available doReturn(new Display[] {mDisplays[1]}).when(mMockedInjector).getAllDisplays(); mHandler.flush(); - PreferenceCategory pref = mPreferenceScreen.findPreference(PrefBasics.DISPLAYS_LIST.key); - assertThat(pref).isNotNull(); - assertThat(pref.getPreferenceCount()).isEqualTo(1); + int attachedId = mDisplays[1].getDisplayId(); + assertDisplayList(true, attachedId); + assertThat(mPreferenceScreen.findPreference( + resolutionRotationPreferenceKey(attachedId))) + .isNotNull(); + assertDisplayList(false, mDisplays[2].getDisplayId()); } @Test @@ -205,8 +232,7 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa // Init initFragment(); mHandler.flush(); - PreferenceCategory list = mPreferenceScreen.findPreference(PrefBasics.DISPLAYS_LIST.key); - assertThat(list).isNull(); + assertDisplayList(false, mDisplays[1].getDisplayId()); var pref = mPreferenceScreen.findPreference(PrefBasics.EXTERNAL_DISPLAY_RESOLUTION.key); assertThat(pref).isNotNull(); pref = mPreferenceScreen.findPreference(PrefBasics.EXTERNAL_DISPLAY_ROTATION.key); @@ -227,8 +253,8 @@ public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBa // Init initFragment(); mHandler.flush(); - PreferenceCategory list = mPreferenceScreen.findPreference(PrefBasics.DISPLAYS_LIST.key); - assertThat(list).isNull(); + assertDisplayList(false, mDisplays[1].getDisplayId()); + assertDisplayList(false, mDisplays[2].getDisplayId()); var pref = mPreferenceScreen.findPreference(PrefBasics.EXTERNAL_DISPLAY_RESOLUTION.key); assertThat(pref).isNotNull(); pref = mPreferenceScreen.findPreference(PrefBasics.EXTERNAL_DISPLAY_ROTATION.key); diff --git a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayTestBase.java b/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayTestBase.java index ea76118c5a2..fcc3daa4efc 100644 --- a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayTestBase.java +++ b/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayTestBase.java @@ -49,6 +49,9 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class ExternalDisplayTestBase { + static final int EXTERNAL_DISPLAY_ID = 1; + static final int OVERLAY_DISPLAY_ID = 2; + @Mock ExternalDisplaySettingsConfiguration.Injector mMockedInjector; @Mock @@ -115,7 +118,7 @@ public class ExternalDisplayTestBase { } Display createExternalDisplay() throws RemoteException { - int displayId = 1; + int displayId = EXTERNAL_DISPLAY_ID; var displayInfo = new DisplayInfo(); doReturn(displayInfo).when(mMockedIDisplayManager).getDisplayInfo(displayId); displayInfo.displayId = displayId; @@ -134,7 +137,7 @@ public class ExternalDisplayTestBase { } Display createOverlayDisplay() throws RemoteException { - int displayId = 2; + int displayId = OVERLAY_DISPLAY_ID; var displayInfo = new DisplayInfo(); doReturn(displayInfo).when(mMockedIDisplayManager).getDisplayInfo(displayId); displayInfo.displayId = displayId; From d7b635fcc2bab6c179bd2f4db40daae9411671c0 Mon Sep 17 00:00:00 2001 From: Ze Li Date: Thu, 13 Feb 2025 14:22:38 +0800 Subject: [PATCH 17/20] [Temp bonding] Move audio sharing settings up Test: com.android.settingslib.bluetooth.CachedBluetoothDeviceTest Bug: 362859132 Flag: com.android.settingslib.flags.enable_temporary_bond_devices_ui Change-Id: I5ed1713d6b51b2156f8e5ffcfcaa003f915b5937 --- res/xml/connected_devices.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml index e8c169ce92c..9642236ad03 100644 --- a/res/xml/connected_devices.xml +++ b/res/xml/connected_devices.xml @@ -26,6 +26,14 @@ settings:allowDividerBelow="true" settings:controller="com.android.settings.slices.SlicePreferenceController" /> + + - - Date: Thu, 13 Feb 2025 07:01:25 +0000 Subject: [PATCH 18/20] Fix TalkBack announces disruptive announcement in PIN/PWD page. Set the description state to A11y Node info for "Must be at least 4 characters" when enter "Set a password", "Must be fewer than 17 digits" in the "Set a PIN" or "Set a password" and "PIN must be at least 4 digit, but a 6-digit PIN is recommended for adding security" Flag: EXEMPT bugfix Bug: 384588020 Bug: 384604918 Bug: 384619356 Test: local build and verify Talkback announcement in PIN/PWD page. Change-Id: Ib81abdc41dba1eeb854f2c7361362b5d55f8c7c7 --- .../settings/password/ChooseLockPassword.java | 1 + .../password/PasswordRequirementAdapter.java | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index 80f73b95c66..def98bac020 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -560,6 +560,7 @@ public class ChooseLockPassword extends SettingsActivity { setupPasswordRequirementsView(headerLayout); mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); + mPasswordRestrictionView.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE); mPasswordEntry = view.findViewById(R.id.password_entry); mPasswordEntry.setOnEditorActionListener(this); mPasswordEntry.addTextChangedListener(this); diff --git a/src/com/android/settings/password/PasswordRequirementAdapter.java b/src/com/android/settings/password/PasswordRequirementAdapter.java index b17f86481df..157a716ce9a 100644 --- a/src/com/android/settings/password/PasswordRequirementAdapter.java +++ b/src/com/android/settings/password/PasswordRequirementAdapter.java @@ -20,9 +20,9 @@ import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.TextView; -import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.android.settings.R; @@ -66,15 +66,20 @@ public class PasswordRequirementAdapter extends return mRequirements[position].hashCode(); } - @Override - public void onViewAttachedToWindow(@NonNull PasswordRequirementViewHolder holder) { - holder.mDescriptionText.announceForAccessibility(holder.mDescriptionText.getText()); - } - @Override public void onBindViewHolder(PasswordRequirementViewHolder holder, int position) { final int fontSize = mContext.getResources().getDimensionPixelSize( R.dimen.password_requirement_font_size); + + final String requirement = mRequirements[position]; + holder.mDescriptionText.setAccessibilityDelegate(new View.AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + holder.mDescriptionText.setStateDescription(requirement); + } + }); + holder.mDescriptionText.setText(mRequirements[position]); if (mIsTooShortError) { holder.mDescriptionText.setTextAppearance(R.style.ScreenLockPasswordHintTextFontStyle); From af4b7112f0efe915462a508033fb5a6b3e8d3f85 Mon Sep 17 00:00:00 2001 From: chelseahao Date: Thu, 13 Feb 2025 15:18:28 +0800 Subject: [PATCH 19/20] Fix npe when broadcast code is null. Test: atest Bug: b/381775542 Flag: com.android.settingslib.flags.enable_le_audio_sharing Change-Id: Id342a4b3ce26927d5ae76c866da07763bf8fbfbe --- res/values/strings.xml | 10 ++-- .../AudioSharingDialogFragment.java | 17 ++++--- .../AudioSharingDialogFragmentTest.java | 50 +++++++++++++++++++ 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index eb316e0a8eb..c5fe6b598bb 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -13956,13 +13956,15 @@ Share with %1$s Close - + Connect another pair of compatible headphones, or share your stream\'s name and password with the other person - + Let others scan this code and listen to your audio\n\nStream name: %1$s\nPassword: %2$s - + + Let others scan this code and listen to your audio\n\nStream name: %1$s + or pair another set of compatible headphones - + Pair another set of compatible headphones, or share your audio stream QR code with the other person Sharing audio diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java index cf71d5f4685..14a559014cf 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java @@ -186,14 +186,17 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment { Drawable qrCodeDrawable = metadata == null ? null : getQrCodeDrawable(metadata, getContext()).orElse(null); if (qrCodeDrawable != null) { + String broadcastName = + metadata.getBroadcastName() == null ? "" : metadata.getBroadcastName(); + boolean hasPassword = metadata.getBroadcastCode() != null + && metadata.getBroadcastCode().length > 0; + String message = hasPassword ? getString( + R.string.audio_sharing_dialog_qr_code_content, broadcastName, + new String(metadata.getBroadcastCode(), StandardCharsets.UTF_8)) : + getString(R.string.audio_sharing_dialog_qr_code_content_no_password, + broadcastName); builder.setCustomImage(qrCodeDrawable) - .setCustomMessage( - getString( - R.string.audio_sharing_dialog_qr_code_content, - metadata.getBroadcastName(), - new String( - metadata.getBroadcastCode(), - StandardCharsets.UTF_8))) + .setCustomMessage(message) .setCustomMessage2(R.string.audio_sharing_dialog_pair_new_device_content) .setCustomNegativeButton(R.string.audio_streams_dialog_close, v -> onCancelClick()); diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java index 145a5c7a549..d964b9cbd79 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java @@ -93,8 +93,14 @@ public class AudioSharingDialogFragmentTest { private static final String METADATA_STR = "BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;" + "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;"; + private static final String METADATA_STR_NO_PASSWORD = + "BLUETOOTH:UUID:184F;BN:SG9ja2V5;AT:0;AD:AABBCC001122;BI:DE51E9;SQ:1;AS:1;PI:FFFF;" + + "NS:1;BS:1;NB:1;;"; private static final BluetoothLeBroadcastMetadata METADATA = BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(METADATA_STR); + private static final BluetoothLeBroadcastMetadata METADATA_NO_PASSWORD = + BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata( + METADATA_STR_NO_PASSWORD); private Fragment mParent; private FakeFeatureFactory mFeatureFactory; @@ -290,6 +296,50 @@ public class AudioSharingDialogFragmentTest { assertThat(dialog.isShowing()).isFalse(); } + @Test + public void onCreateDialog_noExtraConnectedDevice_hasMetadataNoPassword_showCancelButton() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + AtomicBoolean isCancelBtnClicked = new AtomicBoolean(false); + AudioSharingDialogFragment.show( + mParent, + new ArrayList<>(), + METADATA_NO_PASSWORD, + new AudioSharingDialogFragment.DialogEventListener() { + @Override + public void onCancelClick() { + isCancelBtnClicked.set(true); + } + }, + TEST_EVENT_DATA_LIST); + shadowMainLooper().idle(); + AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog).isNotNull(); + ImageView image = dialog.findViewById(R.id.description_image); + assertThat(image).isNotNull(); + TextView text = dialog.findViewById(R.id.description_text); + assertThat(text).isNotNull(); + assertThat(METADATA_NO_PASSWORD).isNotNull(); + assertThat(text.getText().toString()).isEqualTo( + mParent.getString(R.string.audio_sharing_dialog_qr_code_content_no_password, + METADATA_NO_PASSWORD.getBroadcastName())); + TextView textBottom = dialog.findViewById(R.id.description_text_2); + assertThat(textBottom).isNotNull(); + assertThat(textBottom.getText().toString()).isEqualTo( + mParent.getString(R.string.audio_sharing_dialog_pair_new_device_content)); + Button cancelBtn = dialog.findViewById(R.id.negative_btn); + assertThat(cancelBtn).isNotNull(); + cancelBtn.performClick(); + shadowMainLooper().idle(); + + verify(mFeatureFactory.metricsFeatureProvider) + .action( + any(Context.class), + eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED), + eq(TEST_EVENT_DATA)); + assertThat(isCancelBtnClicked.get()).isTrue(); + assertThat(dialog.isShowing()).isFalse(); + } + @Test public void onCreateDialog_flagOn_singleExtraConnectedDevice() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); From 220312eab7b1cb8f19afb4e9646f211a55b73eff Mon Sep 17 00:00:00 2001 From: Ze Li Date: Thu, 13 Feb 2025 15:26:20 +0800 Subject: [PATCH 20/20] [Temp bonding] Clean up old guest device logic as requirements changed Test: manual test Bug: 362859132 Flag: com.android.settingslib.flags.enable_temporary_bond_devices_ui Change-Id: If4a471e2058873be5cdb0ee43371333ebdc9a38a --- res/values/strings.xml | 2 - res/xml/connected_devices.xml | 5 - .../AvailableMediaBluetoothDeviceUpdater.java | 9 - .../ConnectedBluetoothDeviceUpdater.java | 8 - .../ConnectedDeviceDashboardFragment.java | 5 - .../AudioSharingBluetoothDeviceUpdater.java | 9 - ...oSharingCallAudioPreferenceController.java | 11 - .../TemporaryBondDeviceGroupController.java | 170 ------------ .../TemporaryBondDeviceGroupUpdater.java | 74 ------ .../ConnectedBluetoothDeviceUpdaterTest.java | 19 -- .../ConnectedDeviceDashboardFragmentTest.java | 4 +- ...ringCallAudioPreferenceControllerTest.java | 21 -- ...emporaryBondDeviceGroupControllerTest.java | 243 ------------------ .../TemporaryBondDeviceGroupUpdaterTest.java | 139 ---------- 14 files changed, 1 insertion(+), 718 deletions(-) delete mode 100644 src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupController.java delete mode 100644 src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupUpdater.java delete mode 100644 tests/robotests/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupControllerTest.java delete mode 100644 tests/robotests/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupUpdaterTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 089003006cf..c6b82106107 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -305,8 +305,6 @@ Media devices - - Guest devices Call devices diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml index 9642236ad03..06fb5a7941d 100644 --- a/res/xml/connected_devices.xml +++ b/res/xml/connected_devices.xml @@ -40,11 +40,6 @@ settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingDevicePreferenceController"> - - !anyTemporaryBondDevice(entry.getValue())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - } mDeviceItemsInSharingSession = AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem( mBtManager, mGroupedConnectedDevices, /* filterByInSharing= */ true); } - private boolean anyTemporaryBondDevice(List connectedDevices) { - return connectedDevices.stream().anyMatch(BluetoothUtils::isTemporaryBondDevice); - } - @Nullable private Pair getActiveItemWithIndex() { List deviceItems = new ArrayList<>(mDeviceItemsInSharingSession); diff --git a/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupController.java b/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupController.java deleted file mode 100644 index ff3aa2c12e0..00000000000 --- a/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupController.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * 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.connecteddevice.audiosharing; - -import android.content.Context; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.lifecycle.DefaultLifecycleObserver; -import androidx.lifecycle.LifecycleOwner; -import androidx.preference.Preference; -import androidx.preference.PreferenceGroup; -import androidx.preference.PreferenceScreen; - -import com.android.settings.bluetooth.BluetoothDeviceUpdater; -import com.android.settings.bluetooth.Utils; -import com.android.settings.connecteddevice.DevicePreferenceCallback; -import com.android.settings.core.BasePreferenceController; -import com.android.settings.dashboard.DashboardFragment; -import com.android.settingslib.bluetooth.BluetoothCallback; -import com.android.settingslib.bluetooth.BluetoothEventManager; -import com.android.settingslib.bluetooth.BluetoothUtils; -import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.flags.Flags; -import com.android.settingslib.utils.ThreadUtils; - -/** - * Controller to maintain the {@link androidx.preference.PreferenceGroup} for all connected - * temporary bond devices. It uses {@link DevicePreferenceCallback} to add/remove - * {@link Preference} - */ -public class TemporaryBondDeviceGroupController extends BasePreferenceController implements - DefaultLifecycleObserver, DevicePreferenceCallback, BluetoothCallback { - private static final String TAG = "TemporaryBondDeviceGroupController"; - private static final String KEY = "temp_bond_device_list"; - - @Nullable - private final BluetoothEventManager mEventManager; - @Nullable - private PreferenceGroup mPreferenceGroup; - @Nullable - private BluetoothDeviceUpdater mBluetoothDeviceUpdater; - - - public TemporaryBondDeviceGroupController(@NonNull Context context) { - super(context, KEY); - LocalBluetoothManager btManager = Utils.getLocalBtManager(mContext); - mEventManager = btManager == null ? null : btManager.getEventManager(); - } - - @Override - public void onStart(@NonNull LifecycleOwner owner) { - if (!isAvailable()) { - Log.d(TAG, "Skip onStart(), feature is not supported."); - return; - } - if (mEventManager == null) { - Log.d(TAG, "onStart() Bluetooth is not supported on this device"); - return; - } - var unused = ThreadUtils.postOnBackgroundThread(() -> { - mEventManager.registerCallback(this); - if (mBluetoothDeviceUpdater != null) { - mBluetoothDeviceUpdater.registerCallback(); - mBluetoothDeviceUpdater.refreshPreference(); - } - }); - } - - @Override - public void onStop(@NonNull LifecycleOwner owner) { - var unused = ThreadUtils.postOnBackgroundThread(() -> { - if (mBluetoothDeviceUpdater != null) { - mBluetoothDeviceUpdater.unregisterCallback(); - } - if (mEventManager != null) { - mEventManager.unregisterCallback(this); - return; - } - Log.d(TAG, "onStop() Bluetooth is not supported on this device"); - }); - } - - @Override - public void displayPreference(@NonNull PreferenceScreen screen) { - super.displayPreference(screen); - mPreferenceGroup = screen.findPreference(KEY); - if (mPreferenceGroup != null) { - mPreferenceGroup.setVisible(false); - } - - if (isAvailable() && mBluetoothDeviceUpdater != null) { - mBluetoothDeviceUpdater.setPrefContext(screen.getContext()); - mBluetoothDeviceUpdater.forceUpdate(); - } - } - - @Override - public void onDeviceAdded(@NonNull Preference preference) { - if (mPreferenceGroup != null) { - mPreferenceGroup.addPreference(preference); - Log.d(TAG, "Temporary bond device added"); - if (mPreferenceGroup.getPreferenceCount() == 1) { - mPreferenceGroup.setVisible(true); - } - } - } - - @Override - public void onDeviceRemoved(@NonNull Preference preference) { - if (mPreferenceGroup != null) { - mPreferenceGroup.removePreference(preference); - Log.d(TAG, "Temporary bond device removed"); - if (mPreferenceGroup.getPreferenceCount() == 0) { - mPreferenceGroup.setVisible(false); - } - } - } - - @Override - public int getAvailabilityStatus() { - return (BluetoothUtils.isAudioSharingUIAvailable(mContext) - && mBluetoothDeviceUpdater != null && Flags.enableTemporaryBondDevicesUi()) - ? AVAILABLE_UNSEARCHABLE - : UNSUPPORTED_ON_DEVICE; - } - - @Override - public String getPreferenceKey() { - return KEY; - } - - /** - * Initialize the controller. - * - * @param fragment The fragment to provide the context and metrics category for {@link - * TemporaryBondDeviceGroupUpdater} and provide the host for dialogs. - */ - public void init(@NonNull DashboardFragment fragment) { - mBluetoothDeviceUpdater = new TemporaryBondDeviceGroupUpdater(fragment.getContext(), - TemporaryBondDeviceGroupController.this, - fragment.getMetricsCategory()); - } - - @VisibleForTesting - void setBluetoothDeviceUpdater(@Nullable BluetoothDeviceUpdater bluetoothDeviceUpdater) { - mBluetoothDeviceUpdater = bluetoothDeviceUpdater; - } - - @VisibleForTesting - void setPreferenceGroup(@Nullable PreferenceGroup preferenceGroup) { - mPreferenceGroup = preferenceGroup; - } -} diff --git a/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupUpdater.java b/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupUpdater.java deleted file mode 100644 index 59e37c51c08..00000000000 --- a/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupUpdater.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.connecteddevice.audiosharing; - -import android.content.Context; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.android.settings.bluetooth.BluetoothDeviceUpdater; -import com.android.settings.connecteddevice.DevicePreferenceCallback; -import com.android.settingslib.bluetooth.BluetoothUtils; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.settingslib.flags.Flags; - -/** Maintain and update connected temporary bond bluetooth devices */ -public class TemporaryBondDeviceGroupUpdater extends BluetoothDeviceUpdater { - private static final String TAG = "TemporaryBondDeviceGroupUpdater"; - private static final String PREF_KEY_PREFIX = "temp_bond_bt_"; - - public TemporaryBondDeviceGroupUpdater( - @NonNull Context context, - @NonNull DevicePreferenceCallback devicePreferenceCallback, - int metricsCategory) { - super(context, devicePreferenceCallback, metricsCategory); - } - - @Override - public boolean isFilterMatched(@NonNull CachedBluetoothDevice cachedDevice) { - // Only connected temporary bond device should be shown in this section when Audio - // sharing UI is available. - boolean isFilterMatched = Flags.enableTemporaryBondDevicesUi() - && BluetoothUtils.isTemporaryBondDevice(cachedDevice.getDevice()) - && isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice) - && BluetoothUtils.isAudioSharingUIAvailable(mContext); - Log.d( - TAG, - "isFilterMatched() device : " - + cachedDevice.getName() - + ", isFilterMatched : " - + isFilterMatched); - return isFilterMatched; - } - - @Override - protected String getPreferenceKeyPrefix() { - return PREF_KEY_PREFIX; - } - - @Override - protected String getLogTag() { - return TAG; - } - - @Override - protected void update(CachedBluetoothDevice cachedBluetoothDevice) { - super.update(cachedBluetoothDevice); - Log.d(TAG, "Map : " + mPreferenceMap); - } -} diff --git a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java index ad155ffd743..f68a8d4cf6a 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java @@ -68,9 +68,6 @@ public class ConnectedBluetoothDeviceUpdaterTest { private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C"; private static final String TEST_EXCLUSIVE_MANAGER = "com.test.manager"; - private static final String TEMP_BOND_METADATA = - "le_audio_sharing"; - private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @@ -408,22 +405,6 @@ public class ConnectedBluetoothDeviceUpdaterTest { verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice); } - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI) - public void update_temporaryBondDevice_removePreference() { - setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); - when(mBluetoothDeviceUpdater - .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); - when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); - when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)) - .thenReturn(TEMP_BOND_METADATA.getBytes()); - - mBluetoothDeviceUpdater.update(mCachedBluetoothDevice); - - verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice); - verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice); - } - private void setUpDeviceUpdaterWithAudioMode(int audioMode) { mAudioManager.setMode(audioMode); mBluetoothDeviceUpdater = spy(new ConnectedBluetoothDeviceUpdater(mContext, diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java index a1cc1d80acb..ecf6d003cef 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java @@ -75,7 +75,6 @@ public class ConnectedDeviceDashboardFragmentTest { private static final String KEY_AUDIO_SHARING_SETTINGS = "connected_device_audio_sharing_settings"; private static final String KEY_ADD_BT_DEVICES = "add_bt_devices"; - private static final String KEY_TEMPORARY_BOND_DEVICES = "temp_bond_device_list"; private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; private static final String SYSTEMUI_PACKAGE_NAME = "com.android.systemui"; private static final String SLICE_ACTION = "com.android.settings.SEARCH_RESULT_TRAMPOLINE"; @@ -130,8 +129,7 @@ public class ConnectedDeviceDashboardFragmentTest { KEY_SAVED_DEVICE_SEE_ALL, KEY_FAST_PAIR_DEVICE_SEE_ALL, KEY_AUDIO_SHARING_DEVICES, - KEY_AUDIO_SHARING_SETTINGS, - KEY_TEMPORARY_BOND_DEVICES); + KEY_AUDIO_SHARING_SETTINGS); } @Test diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceControllerTest.java index a2ac0cc99c9..4f6fed7b732 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceControllerTest.java @@ -43,7 +43,6 @@ import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.os.Looper; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.view.View; @@ -111,11 +110,8 @@ public class AudioSharingCallAudioPreferenceControllerTest { private static final String PREF_KEY = "calls_and_alarms"; private static final String TEST_DEVICE_NAME1 = "test1"; private static final String TEST_DEVICE_NAME2 = "test2"; - private static final String TEMP_BOND_METADATA = - "le_audio_sharing"; private static final int TEST_DEVICE_GROUP_ID1 = 1; private static final int TEST_DEVICE_GROUP_ID2 = 2; - private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; private static final String TEST_SETTINGS_KEY = "bluetooth_le_broadcast_fallback_active_group_id"; @@ -447,23 +443,6 @@ public class AudioSharingCallAudioPreferenceControllerTest { assertThat(mPreference.getSummary().toString()).isEmpty(); } - @Test - @EnableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI) - public void displayPreference_hasTemporaryBondDevice_doNotShow() { - Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1); - when(mCachedDevice1.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true); - when(mBroadcast.isEnabled(any())).thenReturn(true); - when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1, mDevice2)); - when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState)); - when(mDevice2.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)).thenReturn( - TEMP_BOND_METADATA.getBytes()); - - mController.displayPreference(mScreen); - shadowOf(Looper.getMainLooper()).idle(); - - assertThat(mController.mGroupedConnectedDevices).hasSize(0); - } - @Test public void displayPreference_clickToShowCorrectDialog() { AlertDialog latestAlertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupControllerTest.java deleted file mode 100644 index a85555d36d5..00000000000 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupControllerTest.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * 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.connecteddevice.audiosharing; - -import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothStatusCodes; -import android.content.Context; -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 androidx.lifecycle.LifecycleOwner; -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceGroup; -import androidx.preference.PreferenceManager; -import androidx.preference.PreferenceScreen; -import androidx.test.core.app.ApplicationProvider; - -import com.android.settings.bluetooth.Utils; -import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; -import com.android.settings.testutils.shadow.ShadowBluetoothUtils; -import com.android.settingslib.bluetooth.BluetoothCallback; -import com.android.settingslib.bluetooth.BluetoothEventManager; -import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.flags.Flags; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; -import org.robolectric.shadow.api.Shadow; - -/** Tests for {@link TemporaryBondDeviceGroupController}. */ -@RunWith(RobolectricTestRunner.class) -@Config( - shadows = { - ShadowBluetoothAdapter.class, - ShadowBluetoothUtils.class - }) -public class TemporaryBondDeviceGroupControllerTest { - private static final String KEY = "temp_bond_device_list"; - private static final String PREFERENCE_KEY_1 = "pref_key_1"; - - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - - @Mock - private TemporaryBondDeviceGroupUpdater mBluetoothDeviceUpdater; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private PreferenceManager mPreferenceManager; - @Mock - private LocalBluetoothManager mLocalBtManager; - @Mock - private BluetoothEventManager mEventManager; - @Mock private PreferenceScreen mScreen; - - - private PreferenceGroup mPreferenceGroup; - private Context mContext; - private Preference mPreference; - private TemporaryBondDeviceGroupController mTemporaryBondDeviceGroupController; - private LifecycleOwner mLifecycleOwner; - private Lifecycle mLifecycle; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mContext = ApplicationProvider.getApplicationContext(); - mPreference = new Preference(mContext); - mPreference.setKey(PREFERENCE_KEY_1); - mPreferenceGroup = spy(new PreferenceCategory(mContext)); - when(mPreferenceGroup.getPreferenceManager()).thenReturn(mPreferenceManager); - mLifecycleOwner = () -> mLifecycle; - mLifecycle = new Lifecycle(mLifecycleOwner); - when(mScreen.getContext()).thenReturn(mContext); - when(mScreen.findPreference(KEY)).thenReturn(mPreferenceGroup); - - ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager; - mLocalBtManager = Utils.getLocalBtManager(mContext); - when(mLocalBtManager.getEventManager()).thenReturn(mEventManager); - ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract( - BluetoothAdapter.getDefaultAdapter()); - shadowBluetoothAdapter.setEnabled(true); - shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( - BluetoothStatusCodes.FEATURE_SUPPORTED); - shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( - BluetoothStatusCodes.FEATURE_SUPPORTED); - - mTemporaryBondDeviceGroupController = spy(new TemporaryBondDeviceGroupController(mContext)); - mTemporaryBondDeviceGroupController.setBluetoothDeviceUpdater(mBluetoothDeviceUpdater); - mTemporaryBondDeviceGroupController.setPreferenceGroup(mPreferenceGroup); - } - - @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI) - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void onStart_flagOff_doNothing() { - mTemporaryBondDeviceGroupController.onStart(mLifecycleOwner); - - verify(mEventManager, never()).registerCallback(any(BluetoothCallback.class)); - verify(mBluetoothDeviceUpdater, never()).registerCallback(); - verify(mBluetoothDeviceUpdater, never()).refreshPreference(); - } - - @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI) - public void onStart_audioSharingUINotAvailable_doNothing() { - mTemporaryBondDeviceGroupController.onStart(mLifecycleOwner); - - verify(mEventManager, never()).registerCallback(any(BluetoothCallback.class)); - verify(mBluetoothDeviceUpdater, never()).registerCallback(); - verify(mBluetoothDeviceUpdater, never()).refreshPreference(); - } - - @Test - @RequiresFlagsEnabled({Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI, - Flags.FLAG_ENABLE_LE_AUDIO_SHARING}) - public void onStart_registerCallbacks() { - mTemporaryBondDeviceGroupController.onStart(mLifecycleOwner); - - verify(mEventManager).registerCallback(any(BluetoothCallback.class)); - verify(mBluetoothDeviceUpdater).registerCallback(); - verify(mBluetoothDeviceUpdater).refreshPreference(); - } - - @Test - @RequiresFlagsEnabled({Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI, - Flags.FLAG_ENABLE_LE_AUDIO_SHARING}) - public void onStop_unregisterCallbacks() { - mTemporaryBondDeviceGroupController.onStop(mLifecycleOwner); - - verify(mEventManager).unregisterCallback(any(BluetoothCallback.class)); - verify(mBluetoothDeviceUpdater).unregisterCallback(); - } - - @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI) - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void displayPreference_flagOff_doNothing() { - mTemporaryBondDeviceGroupController.displayPreference(mScreen); - - assertThat(mPreferenceGroup.isVisible()).isFalse(); - verify(mBluetoothDeviceUpdater, never()).forceUpdate(); - } - - @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI) - public void displayPreference_audioSharingUINotAvailable_doNothing() { - mTemporaryBondDeviceGroupController.displayPreference(mScreen); - - assertThat(mPreferenceGroup.isVisible()).isFalse(); - verify(mBluetoothDeviceUpdater, never()).forceUpdate(); - } - - @Test - @RequiresFlagsEnabled({Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI, - Flags.FLAG_ENABLE_LE_AUDIO_SHARING}) - public void displayPreference_updateDeviceList() { - mTemporaryBondDeviceGroupController.displayPreference(mScreen); - - assertThat(mPreferenceGroup.isVisible()).isFalse(); - verify(mBluetoothDeviceUpdater).setPrefContext(mContext); - verify(mBluetoothDeviceUpdater).forceUpdate(); - } - - @Test - public void onDeviceAdded_firstAdd_becomeVisibleAndPreferenceAdded() { - mTemporaryBondDeviceGroupController.onDeviceAdded(mPreference); - - assertThat(mPreferenceGroup.isVisible()).isTrue(); - assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1); - } - - @Test - public void onDeviceRemoved_lastRemove_becomeInvisibleAndPreferenceRemoved() { - mPreferenceGroup.addPreference(mPreference); - - mTemporaryBondDeviceGroupController.onDeviceRemoved(mPreference); - - assertThat(mPreferenceGroup.isVisible()).isFalse(); - assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0); - } - - @Test - public void onDeviceRemoved_notLastRemove_stillVisible() { - mPreferenceGroup.setVisible(true); - mPreferenceGroup.addPreference(mPreference); - mPreferenceGroup.addPreference(new Preference(mContext)); - - mTemporaryBondDeviceGroupController.onDeviceRemoved(mPreference); - - assertThat(mPreferenceGroup.isVisible()).isTrue(); - assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1); - } - - @Test - public void getPreferenceKey_returnsCorrectKey() { - assertThat(mTemporaryBondDeviceGroupController.getPreferenceKey()).isEqualTo(KEY); - } - - @Test - @RequiresFlagsEnabled({Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI, - Flags.FLAG_ENABLE_LE_AUDIO_SHARING}) - public void getAvailabilityStatus_returnsAvailable() { - assertThat(mTemporaryBondDeviceGroupController.getAvailabilityStatus()).isEqualTo( - AVAILABLE_UNSEARCHABLE); - } -} diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupUpdaterTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupUpdaterTest.java deleted file mode 100644 index 0b34c101719..00000000000 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/TemporaryBondDeviceGroupUpdaterTest.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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.connecteddevice.audiosharing; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothStatusCodes; -import android.content.Context; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; - -import androidx.test.core.app.ApplicationProvider; - -import com.android.settings.bluetooth.Utils; -import com.android.settings.connecteddevice.DevicePreferenceCallback; -import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; -import com.android.settings.testutils.shadow.ShadowBluetoothUtils; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; -import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.flags.Flags; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; -import org.robolectric.shadow.api.Shadow; - -import java.util.ArrayList; -import java.util.Collection; - -/** Tests for {@link TemporaryBondDeviceGroupUpdater}. */ -@RunWith(RobolectricTestRunner.class) -@Config( - shadows = { - ShadowBluetoothAdapter.class, - ShadowBluetoothUtils.class - }) -public class TemporaryBondDeviceGroupUpdaterTest { - private static final String TAG = "TemporaryBondDeviceGroupUpdater"; - private static final String PREF_KEY_PREFIX = "temp_bond_bt_"; - private static final String TEMP_BOND_METADATA = - "le_audio_sharing"; - private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; - - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - - @Mock - private DevicePreferenceCallback mDevicePreferenceCallback; - @Mock - private CachedBluetoothDevice mCachedBluetoothDevice; - @Mock - private BluetoothDevice mBluetoothDevice; - @Mock - private LocalBluetoothManager mLocalBtManager; - @Mock - private CachedBluetoothDeviceManager mCachedDeviceManager; - - private TemporaryBondDeviceGroupUpdater mDeviceUpdater; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract( - BluetoothAdapter.getDefaultAdapter()); - shadowBluetoothAdapter.setEnabled(true); - shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( - BluetoothStatusCodes.FEATURE_SUPPORTED); - shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( - BluetoothStatusCodes.FEATURE_SUPPORTED); - Context context = ApplicationProvider.getApplicationContext(); - ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager; - mLocalBtManager = Utils.getLocalBtManager(context); - when(mLocalBtManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); - when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); - Collection cachedDevices = new ArrayList<>(); - cachedDevices.add(mCachedBluetoothDevice); - when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices); - mDeviceUpdater = - spy( - new TemporaryBondDeviceGroupUpdater( - context, mDevicePreferenceCallback, /* metricsCategory= */ 0)); - mDeviceUpdater.setPrefContext(context); - } - - @After - public void tearDown() { - ShadowBluetoothUtils.reset(); - } - - @Test - @RequiresFlagsEnabled({Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI, - Flags.FLAG_ENABLE_LE_AUDIO_SHARING}) - public void isFilterMatched_isTemporaryBondDevice_returnsTrue() { - when(mBluetoothDevice.isConnected()).thenReturn(true); - when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)) - .thenReturn(TEMP_BOND_METADATA.getBytes()); - - assertThat(mDeviceUpdater.isFilterMatched(mCachedBluetoothDevice)).isTrue(); - } - - @Test - public void getLogTag_returnsCorrectTag() { - assertThat(mDeviceUpdater.getLogTag()).isEqualTo(TAG); - } - - @Test - public void getPreferenceKey_returnsCorrectKey() { - assertThat(mDeviceUpdater.getPreferenceKeyPrefix()).isEqualTo(PREF_KEY_PREFIX); - } -}