From 464c14649a6c71c542c55cfcc89ff0f4eb227046 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Thu, 10 Oct 2024 16:51:09 +0800 Subject: [PATCH 01/13] Use getEnabledProfiles for ProfileSelectDialog In EnableContentCaptureWithServiceSettingsPreferenceController. Which follows current examples OnDeviceRecognitionPreferenceController and ZenModePrioritySendersPreferenceController. Fix: 368646940 Flag: EXEMPT bug fix Test: manual - on "More security & privacy" page Change-Id: I82cc9ba89ff8ef5e21e0ea7539681726b3c69e90 --- ...entCaptureWithServiceSettingsPreferenceController.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java b/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java index c8a54712067..46aed3c0022 100644 --- a/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java +++ b/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java @@ -19,7 +19,6 @@ package com.android.settings.privacy; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; @@ -33,7 +32,6 @@ import com.android.settings.core.TogglePreferenceController; import com.android.settings.dashboard.profileselector.ProfileSelectDialog; import com.android.settings.utils.ContentCaptureUtils; -import java.util.ArrayList; import java.util.List; public final class EnableContentCaptureWithServiceSettingsPreferenceController @@ -94,11 +92,7 @@ public final class EnableContentCaptureWithServiceSettingsPreferenceController private void show(Preference preference) { final UserManager userManager = UserManager.get(mContext); - final List userInfos = userManager.getUsers(); - final ArrayList userHandles = new ArrayList<>(userInfos.size()); - for (UserInfo info : userInfos) { - userHandles.add(info.getUserHandle()); - } + final List userHandles = userManager.getEnabledProfiles(); final Intent intent = preference.getIntent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); if (userHandles.size() == 1) { mContext.startActivityAsUser(intent, userHandles.get(0)); From 9832fe928108b2016c6444fbc0eb2b219bc02dbe Mon Sep 17 00:00:00 2001 From: Tom Hsu Date: Fri, 11 Oct 2024 04:12:06 +0000 Subject: [PATCH 02/13] Avoid test flaky flag: EXEMPT bugfix fix: 365673174 Test: atest passed Change-Id: Id7c3747eafff3410ab899716d0c6e35eb405c76e Merged-In: Id1290e6979fdafa93ded6f50e49d6b3464a80b50 --- .../src/com/android/settings/vpn2/VpnSettingsTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java b/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java index 953a524750d..8764d4f7a79 100644 --- a/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java +++ b/tests/unit/src/com/android/settings/vpn2/VpnSettingsTest.java @@ -130,6 +130,7 @@ public class VpnSettingsTest { } @Test + @UiThreadTest public void setShownAdvancedPreferences_hasGeneralVpn_returnsVpnCountAs1() { Set updates = new ArraySet<>(); AppPreference pref = @@ -144,6 +145,7 @@ public class VpnSettingsTest { } @Test + @UiThreadTest public void setShownAdvancedPreferences_hasAdvancedVpn_returnsAdvancedVpnCountAs1() { Set updates = new ArraySet<>(); AppPreference pref = @@ -158,6 +160,7 @@ public class VpnSettingsTest { } @Test + @UiThreadTest public void setShownAdvancedPreferences_noVpn_returnsEmpty() { Set updates = new ArraySet<>(); @@ -198,6 +201,7 @@ public class VpnSettingsTest { } @Test + @UiThreadTest public void clickVpn_VpnConnected_doesNotStartVpnLaunchIntent() throws PackageManager.NameNotFoundException { Set updates = new ArraySet<>(); @@ -217,6 +221,7 @@ public class VpnSettingsTest { } @Test + @UiThreadTest public void clickVpn_VpnDisconnected_startsVpnLaunchIntent() throws PackageManager.NameNotFoundException { Set updates = new ArraySet<>(); @@ -238,6 +243,7 @@ public class VpnSettingsTest { } @Test + @UiThreadTest public void clickAdvancedVpn_VpnConnectedDisconnectDialogDisabled_startsAppLaunchIntent() throws PackageManager.NameNotFoundException { Set updates = new ArraySet<>(); @@ -262,6 +268,7 @@ public class VpnSettingsTest { } @Test + @UiThreadTest public void clickAdvancedVpn_VpnConnectedDisconnectDialogEnabled_doesNotStartAppLaunchIntent() throws PackageManager.NameNotFoundException { Set updates = new ArraySet<>(); From f50ba4c6ece3ce5d88824a008042b1490adead49 Mon Sep 17 00:00:00 2001 From: SongFerng Wang Date: Fri, 11 Oct 2024 07:55:17 +0000 Subject: [PATCH 03/13] Update VPN app dialog message to include version code Add the string back to AppDialog in VPN Bug: 372179228 Change-Id: I2f45515623cd7d0fbaf4a17026d040107b804fed Test: verify the UI. Flag: EXEMPT bugfix --- res/values/strings.xml | 2 ++ src/com/android/settings/vpn2/AppDialog.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index a11acb09586..1f9aa617853 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6957,6 +6957,8 @@ Disconnect Version + + Version %s Forget VPN diff --git a/src/com/android/settings/vpn2/AppDialog.java b/src/com/android/settings/vpn2/AppDialog.java index 815b28bf27b..b1229992855 100644 --- a/src/com/android/settings/vpn2/AppDialog.java +++ b/src/com/android/settings/vpn2/AppDialog.java @@ -56,7 +56,7 @@ class AppDialog extends AlertDialog implements DialogInterface.OnClickListener { @Override protected void onCreate(Bundle savedState) { setTitle(mLabel); - setMessage(getContext().getString(R.string.vpn_version, mPackageInfo.versionName)); + setMessage(getContext().getString(R.string.vpn_version_info, mPackageInfo.versionName)); createButtons(); super.onCreate(savedState); From b2dd2e320318e6d6365d3498ea77890829303cca Mon Sep 17 00:00:00 2001 From: Geoffrey Boullanger Date: Fri, 11 Oct 2024 16:02:59 +0000 Subject: [PATCH 04/13] Updated toggles in Date and time settings page Design doc: go/dd-android-settings-time-2024 Changes: - toggling off "automatic time zone" now sets "use location" off and makes it unmodifiable - removing "use locale default" for time format Bug: 296835792 Test: on-device and atest Flag: com.android.settings.flags.revamp_toggles Change-Id: I31744f104fed06ee9980a6a0160501325175a02d --- ...ettings_datetime_flag_declarations.aconfig | 8 ++ res/values/strings.xml | 2 +- res/xml/date_time_prefs_revamped.xml | 104 ++++++++++++++++++ .../AutoTimeZonePreferenceController.java | 32 ++++-- .../settings/datetime/DateTimeSettings.java | 7 +- ...TimeZoneDetectionPreferenceController.java | 35 +++++- .../TimeFormatPreferenceController.java | 11 +- .../AutoTimeZonePreferenceControllerTest.java | 59 +++++++++- ...ZoneDetectionPreferenceControllerTest.java | 36 +++++- .../TimeFormatPreferenceControllerTest.java | 9 ++ 10 files changed, 282 insertions(+), 21 deletions(-) create mode 100644 res/xml/date_time_prefs_revamped.xml diff --git a/aconfig/settings_datetime_flag_declarations.aconfig b/aconfig/settings_datetime_flag_declarations.aconfig index 3d9d8b317a5..93d41d6d5cb 100644 --- a/aconfig/settings_datetime_flag_declarations.aconfig +++ b/aconfig/settings_datetime_flag_declarations.aconfig @@ -9,3 +9,11 @@ flag { bug: "283239837" } +flag { + name: "revamp_toggles" + # "location" is used by the Android System Time team for feature flags. + namespace: "location" + description: "Makes the use location toggle dependent on automatic time zone detection" + bug: "296835792" +} + diff --git a/res/values/strings.xml b/res/values/strings.xml index a11acb09586..50c50a19299 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -579,7 +579,7 @@ Set automatically based on mobile networks near you - Set automatically using your device location, if available. An active Wifi connection may also be required. + Set automatically using your device location, if available Use locale default diff --git a/res/xml/date_time_prefs_revamped.xml b/res/xml/date_time_prefs_revamped.xml new file mode 100644 index 00000000000..fe0fd7e2c52 --- /dev/null +++ b/res/xml/date_time_prefs_revamped.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java b/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java index 2f062897b48..3d8f8011dda 100644 --- a/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java +++ b/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java @@ -32,6 +32,7 @@ import androidx.preference.Preference; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; +import com.android.settings.flags.Flags; public class AutoTimeZonePreferenceController extends TogglePreferenceController { @@ -46,7 +47,7 @@ public class AutoTimeZonePreferenceController extends TogglePreferenceController // setTimeAndDateCallback() isn't called, e.g. for slices and other cases where the // controller is instantiated outside of the context of the real Date & Time settings // screen. - mCallback = (c) -> {}; + mCallback = (c) -> {}; } /** @@ -103,10 +104,25 @@ public class AutoTimeZonePreferenceController extends TogglePreferenceController @Override public boolean setChecked(boolean isChecked) { - TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder() - .setAutoDetectionEnabled(isChecked) - .build(); - boolean result = mTimeManager.updateTimeZoneConfiguration(configuration); + TimeZoneConfiguration.Builder configuration = new TimeZoneConfiguration.Builder() + .setAutoDetectionEnabled(isChecked); + + if (Flags.revampToggles()) { + // "Use location for time zone" is only used if "Automatic time zone" is enabled. If + // the user toggles off automatic time zone, set the toggle off and disable the toggle. + int geoDetectionCapability = mTimeManager + .getTimeZoneCapabilitiesAndConfig() + .getCapabilities() + .getConfigureGeoDetectionEnabledCapability(); + + if (!isChecked + && (geoDetectionCapability == CAPABILITY_NOT_APPLICABLE + || geoDetectionCapability == CAPABILITY_POSSESSED)) { + configuration.setGeoDetectionEnabled(false); + } + } + + boolean result = mTimeManager.updateTimeZoneConfiguration(configuration.build()); mCallback.updateTimeAndDateDisplay(mContext); return result; @@ -138,8 +154,10 @@ public class AutoTimeZonePreferenceController extends TogglePreferenceController @VisibleForTesting boolean isEnabled() { - TimeZoneConfiguration config = getTimeZoneCapabilitiesAndConfig().getConfiguration(); - return config.isAutoDetectionEnabled(); + return mTimeManager + .getTimeZoneCapabilitiesAndConfig() + .getConfiguration() + .isAutoDetectionEnabled(); } private TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig() { diff --git a/src/com/android/settings/datetime/DateTimeSettings.java b/src/com/android/settings/datetime/DateTimeSettings.java index f3c11d43ac3..e5c13bfec18 100644 --- a/src/com/android/settings/datetime/DateTimeSettings.java +++ b/src/com/android/settings/datetime/DateTimeSettings.java @@ -23,6 +23,7 @@ import android.content.Context; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.flags.Flags; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.search.SearchIndexable; @@ -49,6 +50,9 @@ public class DateTimeSettings extends DashboardFragment implements @Override protected int getPreferenceScreenResId() { + if (Flags.revampToggles()) { + return R.xml.date_time_prefs_revamped; + } return R.xml.date_time_prefs; } @@ -119,5 +123,6 @@ public class DateTimeSettings extends DashboardFragment implements } public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider(R.xml.date_time_prefs); + new BaseSearchIndexProvider( + Flags.revampToggles() ? R.xml.date_time_prefs_revamped : R.xml.date_time_prefs); } diff --git a/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java b/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java index a50ce4c3c22..52d49ac8b78 100644 --- a/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java +++ b/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java @@ -32,6 +32,7 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.core.TogglePreferenceController; +import com.android.settings.flags.Flags; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; @@ -64,8 +65,10 @@ public class LocationTimeZoneDetectionPreferenceController @Override public boolean isChecked() { + // forceRefresh set to true as the location toggle may have been turned off by switching off + // automatic time zone TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = - getTimeZoneCapabilitiesAndConfig(/*forceRefresh=*/false); + getTimeZoneCapabilitiesAndConfig(/*forceRefresh=*/ Flags.revampToggles()); TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration(); return configuration.isGeoDetectionEnabled(); } @@ -73,7 +76,7 @@ public class LocationTimeZoneDetectionPreferenceController @Override public boolean setChecked(boolean isChecked) { TimeZoneCapabilitiesAndConfig timeZoneCapabilitiesAndConfig = - getTimeZoneCapabilitiesAndConfig(/*forceRefresh=*/false); + getTimeZoneCapabilitiesAndConfig(/*forceRefresh=*/ false); boolean isLocationEnabled = timeZoneCapabilitiesAndConfig.getCapabilities().isUseLocationEnabled(); if (isChecked && !isLocationEnabled) { @@ -130,17 +133,30 @@ public class LocationTimeZoneDetectionPreferenceController getTimeZoneCapabilitiesAndConfig(/* forceRefresh= */ false).getCapabilities(); int capability = timeZoneCapabilities.getConfigureGeoDetectionEnabledCapability(); - // The preference only has two states: present and not present. The preference is never - // present but disabled. + // The preference can be present and enabled, present and disabled or not present. if (capability == CAPABILITY_NOT_SUPPORTED || capability == CAPABILITY_NOT_ALLOWED) { return UNSUPPORTED_ON_DEVICE; } else if (capability == CAPABILITY_NOT_APPLICABLE || capability == CAPABILITY_POSSESSED) { - return AVAILABLE; + if (Flags.revampToggles()) { + return isAutoTimeZoneEnabled() ? AVAILABLE : DISABLED_DEPENDENT_SETTING; + } else { + return AVAILABLE; + } } else { throw new IllegalStateException("Unknown capability=" + capability); } } + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + if (Flags.revampToggles()) { + // enable / disable the toggle based on automatic time zone being enabled or not + preference.setEnabled(isAutoTimeZoneEnabled()); + } + } + @Override public CharSequence getSummary() { TimeZoneCapabilitiesAndConfig timeZoneCapabilitiesAndConfig = @@ -212,4 +228,13 @@ public class LocationTimeZoneDetectionPreferenceController } return mTimeZoneCapabilitiesAndConfig; } + + /** + * Returns whether the user can select this preference or not, as it is a sub toggle of + * automatic time zone. + */ + private boolean isAutoTimeZoneEnabled() { + return mTimeManager.getTimeZoneCapabilitiesAndConfig().getConfiguration() + .isAutoDetectionEnabled(); + } } diff --git a/src/com/android/settings/datetime/TimeFormatPreferenceController.java b/src/com/android/settings/datetime/TimeFormatPreferenceController.java index 19805ad03dd..2dee76e9fbd 100644 --- a/src/com/android/settings/datetime/TimeFormatPreferenceController.java +++ b/src/com/android/settings/datetime/TimeFormatPreferenceController.java @@ -25,6 +25,7 @@ import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; +import com.android.settings.flags.Flags; import java.util.Calendar; import java.util.Date; @@ -72,8 +73,10 @@ public class TimeFormatPreferenceController extends TogglePreferenceController { if (mIsFromSUW) { return DISABLED_DEPENDENT_SETTING; } - if (AutoTimeFormatPreferenceController.isAutoTimeFormatSelection(mContext)) { - return DISABLED_DEPENDENT_SETTING; + if (!Flags.revampToggles()) { + if (AutoTimeFormatPreferenceController.isAutoTimeFormatSelection(mContext)) { + return DISABLED_DEPENDENT_SETTING; + } } return AVAILABLE; } @@ -130,7 +133,7 @@ public class TimeFormatPreferenceController extends TogglePreferenceController { timeFormatPreference = Intent.EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT; } else { timeFormatPreference = is24Hour ? Intent.EXTRA_TIME_PREF_VALUE_USE_24_HOUR - : Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR; + : Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR; } timeChanged.putExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, timeFormatPreference); context.sendBroadcast(timeChanged); @@ -138,7 +141,7 @@ public class TimeFormatPreferenceController extends TogglePreferenceController { static void set24Hour(Context context, Boolean is24Hour) { String value = is24Hour == null ? null : - is24Hour ? HOURS_24 : HOURS_12; + is24Hour ? HOURS_24 : HOURS_12; Settings.System.putString(context.getContentResolver(), Settings.System.TIME_12_24, value); } diff --git a/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java index 651915b4cab..7bf8d52543e 100644 --- a/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java @@ -40,12 +40,17 @@ import android.app.time.TimeZoneConfiguration; import android.app.time.TimeZoneDetectorStatus; import android.content.Context; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.preference.Preference; import com.android.settings.R; +import com.android.settings.flags.Flags; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -57,6 +62,9 @@ import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class AutoTimeZonePreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private UpdateTimeAndDateCallback mCallback; private Context mContext; @@ -238,8 +246,53 @@ public class AutoTimeZonePreferenceControllerTest { mContext.getString(R.string.auto_zone_requires_location_summary)); } + @Test + @EnableFlags({Flags.FLAG_REVAMP_TOGGLES}) + public void toggleOff_revampFlagOn_shouldToggleOffUseLocation() { + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig( + /* autoSupported= */ true, + /* autoEnabled= */ true, + /* telephonySupported= */ true, + /* locationSupported= */ true); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + mController.setChecked(false); + + TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder() + .setAutoDetectionEnabled(false) + .setGeoDetectionEnabled(false) + .build(); + + verify(mTimeManager).updateTimeZoneConfiguration(configuration); + } + + @Test + @DisableFlags({Flags.FLAG_REVAMP_TOGGLES}) + public void toggleOff_revampFlagOff_shouldToggleOffUseLocation() { + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig( + /* autoSupported= */ true, + /* autoEnabled= */ true, + /* telephonySupported= */ true, + /* locationSupported= */ true); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + mController.setChecked(false); + + TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder() + .setAutoDetectionEnabled(false) + .build(); + + verify(mTimeManager).updateTimeZoneConfiguration(configuration); + } + private static TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig( boolean autoSupported, boolean autoEnabled, boolean telephonySupported) { + return createCapabilitiesAndConfig(autoSupported, autoEnabled, telephonySupported, false); + } + + private static TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig( + boolean autoSupported, boolean autoEnabled, boolean telephonySupported, + boolean locationSupported) { TimeZoneDetectorStatus status = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING, new TelephonyTimeZoneAlgorithmStatus( telephonySupported ? DETECTION_ALGORITHM_STATUS_RUNNING @@ -253,12 +306,14 @@ public class AutoTimeZonePreferenceControllerTest { TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(UserHandle.SYSTEM) .setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability) .setUseLocationEnabled(true) - .setConfigureGeoDetectionEnabledCapability(Capabilities.CAPABILITY_NOT_SUPPORTED) + .setConfigureGeoDetectionEnabledCapability( + locationSupported ? Capabilities.CAPABILITY_POSSESSED + : Capabilities.CAPABILITY_NOT_SUPPORTED) .setSetManualTimeZoneCapability(Capabilities.CAPABILITY_POSSESSED) .build(); TimeZoneConfiguration config = new TimeZoneConfiguration.Builder() .setAutoDetectionEnabled(autoEnabled) - .setGeoDetectionEnabled(false) + .setGeoDetectionEnabled(locationSupported) .build(); return new TimeZoneCapabilitiesAndConfig(status, capabilities, config); } diff --git a/tests/robotests/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceControllerTest.java index cd89c0c2d22..40794d217ee 100644 --- a/tests/robotests/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceControllerTest.java @@ -25,6 +25,8 @@ import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING; import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT; import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY; +import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -43,11 +45,17 @@ import android.app.time.TimeZoneConfiguration; import android.app.time.TimeZoneDetectorStatus; import android.content.Context; import android.os.UserHandle; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.flags.Flags; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; @@ -62,9 +70,14 @@ import org.robolectric.annotation.Config; com.android.settings.testutils.shadow.ShadowFragment.class, }) public class LocationTimeZoneDetectionPreferenceControllerTest { + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private TimeManager mTimeManager; private Context mContext; + private SwitchPreference mPreference; private LocationTimeZoneDetectionPreferenceController mController; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private InstrumentedPreferenceFragment mFragment; @@ -76,6 +89,9 @@ public class LocationTimeZoneDetectionPreferenceControllerTest { when(mContext.getSystemService(TimeManager.class)).thenReturn(mTimeManager); mController = new LocationTimeZoneDetectionPreferenceController(mContext); mController.setFragment(mFragment); + + mPreference = new SwitchPreference(mContext); + mPreference.setKey("location_time_zone_detection"); } @Test @@ -114,6 +130,17 @@ public class LocationTimeZoneDetectionPreferenceControllerTest { verify(mTimeManager, never()).updateTimeZoneConfiguration(any()); } + @Test + @EnableFlags({Flags.FLAG_REVAMP_TOGGLES}) + public void flagRevampTogglesOn_toggleOff_automaticTimeZone_disablesLocationToggle() { + TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = + createTimeZoneCapabilitiesAndConfig(/* useLocationEnabled= */ true, + CAPABILITY_POSSESSED, /* setAutoDetectionEnabled= */ false); + when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + @Test public void setChecked_withFalse_shouldUpdateSetting() { boolean useLocationEnabled = false; @@ -181,7 +208,14 @@ public class LocationTimeZoneDetectionPreferenceControllerTest { private static TimeZoneCapabilitiesAndConfig createTimeZoneCapabilitiesAndConfig( boolean useLocationEnabled, @CapabilityState int configureGeoDetectionEnabledCapability) { + return createTimeZoneCapabilitiesAndConfig(useLocationEnabled, + configureGeoDetectionEnabledCapability, /* setAutoDetectionEnabled= */ true); + } + private static TimeZoneCapabilitiesAndConfig createTimeZoneCapabilitiesAndConfig( + boolean useLocationEnabled, + @CapabilityState int configureGeoDetectionEnabledCapability, + boolean setAutoDetectionEnabled) { // Create a status that matches the user's capability state. LocationTimeZoneAlgorithmStatus locationAlgorithmStatus; switch (configureGeoDetectionEnabledCapability) { @@ -213,7 +247,7 @@ public class LocationTimeZoneDetectionPreferenceControllerTest { .build(); TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder() - .setAutoDetectionEnabled(true) + .setAutoDetectionEnabled(setAutoDetectionEnabled) .setGeoDetectionEnabled(true) .build(); diff --git a/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java index e1ea8f9e0fa..c5aac843018 100644 --- a/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java @@ -23,11 +23,16 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.content.Intent; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import androidx.preference.SwitchPreference; +import com.android.settings.flags.Flags; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -41,6 +46,9 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) public class TimeFormatPreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private UpdateTimeAndDateCallback mCallback; @@ -97,6 +105,7 @@ public class TimeFormatPreferenceControllerTest { } @Test + @DisableFlags({Flags.FLAG_REVAMP_TOGGLES}) public void updateState_autoSet_shouldNotEnablePreference() { Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24, null); From cbca375f20502d0b159081c20dba7cedab0450b5 Mon Sep 17 00:00:00 2001 From: YK Hung Date: Sun, 13 Oct 2024 09:59:29 +0000 Subject: [PATCH 05/13] Update the forEach() into for-loop Test: atest Bug: na Flag: EXEMPT for simple fix Change-Id: I2cf116d4d9f0f6d36f624d89579a12b4a2e0aaf0 --- .../BatterySettingsMigrateChecker.java | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java b/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java index 066e0fc15b6..3714a11e36e 100644 --- a/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java +++ b/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java @@ -72,25 +72,22 @@ public final class BatterySettingsMigrateChecker extends BroadcastReceiver { Context context, @BatteryOptimizeUtils.OptimizationMode int optimizationMode, List allowList) { - allowList.forEach( - packageName -> { - final BatteryOptimizeUtils batteryOptimizeUtils = - BatteryBackupHelper.newBatteryOptimizeUtils( - context, - packageName, - /* testOptimizeUtils */ sBatteryOptimizeUtils); - if (batteryOptimizeUtils == null) { - return; - } - if (batteryOptimizeUtils.getAppOptimizationMode() != optimizationMode) { - Log.w( - TAG, - "Reset " + packageName + " battery mode into " + optimizationMode); - batteryOptimizeUtils.setAppUsageState( - optimizationMode, - BatteryOptimizeHistoricalLogEntry.Action.FORCE_RESET); - } - }); + for (String packageName : allowList) { + final BatteryOptimizeUtils batteryOptimizeUtils = + BatteryBackupHelper.newBatteryOptimizeUtils( + context, + packageName, + /* testOptimizeUtils */ sBatteryOptimizeUtils); + if (batteryOptimizeUtils == null) { + continue; + } + if (batteryOptimizeUtils.getAppOptimizationMode() != optimizationMode) { + Log.w(TAG, "Reset " + packageName + " mode into " + optimizationMode); + batteryOptimizeUtils.setAppUsageState( + optimizationMode, + BatteryOptimizeHistoricalLogEntry.Action.FORCE_RESET); + } + } } static void verifySaverConfiguration(Context context) { From efe8d52f77e713acd26ca3c3c0a88559e53459bd Mon Sep 17 00:00:00 2001 From: Weng Su Date: Mon, 14 Oct 2024 07:02:18 +0800 Subject: [PATCH 06/13] Connect to OWE Wi-Fi network when QR code has no password - Since there is no password for Wi-Fi security in OPEN and OWE, try to connect to one of them Bug: 360377330 Flag: EXEMPT bugfix Test: Manual testing atest -c WifiDppQrCodeScannerFragmentTest Change-Id: Ia0a59e099b724170ad993df8b4246b94e0da392c --- .../dpp/WifiDppQrCodeScannerFragment.java | 31 +++++++++++++------ .../dpp/WifiDppQrCodeScannerFragmentTest.java | 25 +++++++++++++++ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java index 4723b0eacd8..118ff71a519 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java +++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java @@ -262,16 +262,9 @@ public class WifiDppQrCodeScannerFragment extends WifiDppQrCodeBaseFragment impl continue; } canFindNetwork = true; - final int security = - WifiDppUtils.getSecurityTypeFromWifiConfiguration(wifiConfiguration); - if (security == wifiEntry.getSecurity()) { - return REACHABLE_WIFI_NETWORK; - } - - // Default security type of PSK/SAE transition mode WifiEntry is SECURITY_PSK and - // there is no way to know if a WifiEntry is of transition mode. Give it a chance. - if (security == WifiEntry.SECURITY_SAE - && wifiEntry.getSecurity() == WifiEntry.SECURITY_PSK) { + int security = WifiDppUtils.getSecurityTypeFromWifiConfiguration(wifiConfiguration); + if (isSecurityMatched(security, wifiEntry.getSecurity())) { + Log.d(TAG, "WiFi DPP detects connection security for a matching WiFi network."); return REACHABLE_WIFI_NETWORK; } } @@ -283,6 +276,24 @@ public class WifiDppQrCodeScannerFragment extends WifiDppQrCodeBaseFragment impl return R.string.wifi_dpp_check_connection_no_matched_ssid; } + @VisibleForTesting + boolean isSecurityMatched(int qrSecurity, int entrySecurity) { + if (qrSecurity == entrySecurity) { + return true; + } + // Default security type of PSK/SAE transition mode WifiEntry is SECURITY_PSK and + // there is no way to know if a WifiEntry is of transition mode. Give it a chance. + if (qrSecurity == WifiEntry.SECURITY_SAE && entrySecurity == WifiEntry.SECURITY_PSK) { + return true; + } + // If configured is no password, the Wi-Fi framework will attempt OPEN and OWE security. + return isNoPasswordSecurity(qrSecurity) && isNoPasswordSecurity(entrySecurity); + } + + private boolean isNoPasswordSecurity(int security) { + return security == WifiEntry.SECURITY_NONE || security == WifiEntry.SECURITY_OWE; + } + @VisibleForTesting boolean canConnectWifi(String ssid) { final List wifiEntries = mWifiPickerTracker.getWifiEntries(); diff --git a/tests/robotests/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragmentTest.java b/tests/robotests/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragmentTest.java index ff1125de656..9f5fcf8b72c 100644 --- a/tests/robotests/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragmentTest.java +++ b/tests/robotests/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragmentTest.java @@ -16,6 +16,11 @@ package com.android.settings.wifi.dpp; +import static com.android.wifitrackerlib.WifiEntry.SECURITY_NONE; +import static com.android.wifitrackerlib.WifiEntry.SECURITY_OWE; +import static com.android.wifitrackerlib.WifiEntry.SECURITY_PSK; +import static com.android.wifitrackerlib.WifiEntry.SECURITY_SAE; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; @@ -148,4 +153,24 @@ public class WifiDppQrCodeScannerFragmentTest { verify(mActivity).setResult(eq(Activity.RESULT_OK), any()); verify(mActivity).finish(); } + + @Test + public void isSecurityMatched_securityNotMatch_returnFalse() { + assertThat(mFragment.isSecurityMatched(SECURITY_NONE, SECURITY_PSK)).isFalse(); + } + + @Test + public void isSecurityMatched_securityMatch_returnTrue() { + assertThat(mFragment.isSecurityMatched(SECURITY_PSK, SECURITY_PSK)).isTrue(); + } + + @Test + public void isSecurityMatched_tryPskSaeTransition_returnTrue() { + assertThat(mFragment.isSecurityMatched(SECURITY_SAE, SECURITY_PSK)).isTrue(); + } + + @Test + public void isSecurityMatched_noPasswordSecurity_returnTrue() { + assertThat(mFragment.isSecurityMatched(SECURITY_NONE, SECURITY_OWE)).isTrue(); + } } From 44dfbe67480739c3bdc1aee5b8482db8bccb0da7 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Mon, 14 Oct 2024 12:55:45 +0800 Subject: [PATCH 07/13] Ignore catalyst test in DisplayScreenTest Bug: 373177618 Bug: 368359268 Flag: EXEMPT fix test Test: atest Change-Id: I8de8468f53f4b9ec2c66e8e836bf9cf9072e6e63 --- .../src/com/android/settings/display/DisplayScreenTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/robotests/src/com/android/settings/display/DisplayScreenTest.kt b/tests/robotests/src/com/android/settings/display/DisplayScreenTest.kt index d869b84da4b..6a7c2388622 100644 --- a/tests/robotests/src/com/android/settings/display/DisplayScreenTest.kt +++ b/tests/robotests/src/com/android/settings/display/DisplayScreenTest.kt @@ -23,6 +23,7 @@ import com.android.settings.flags.Flags import com.android.settings.testutils.FakeFeatureFactory import com.android.settingslib.preference.CatalystScreenTestCase import com.google.common.truth.Truth.assertThat +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt @@ -65,6 +66,7 @@ class DisplayScreenTest : CatalystScreenTestCase() { assertThat(preferenceScreenCreator.isAvailable(contextWrapper)).isFalse() } + @Ignore("robolectric.createActivityContexts cause other test failure") override fun migration() { // avoid UnsupportedOperationException when getDisplay from context System.setProperty("robolectric.createActivityContexts", "true") From 44527db356307b534f4df83f3fcca396ef0b336f Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 14 Oct 2024 13:24:14 +0800 Subject: [PATCH 08/13] Fix PhoneNumberPreferenceControllerTest Fix: 373247298 Flag: EXEMPT test only Test: atest PhoneNumberPreferenceControllerTest Change-Id: Ied4c46beca9130ea2db469e3f5dfe708c84b7373 --- .../PhoneNumberPreferenceControllerTest.kt | 135 +++++++++++++++ .../PhoneNumberPreferenceControllerTest.java | 158 ------------------ 2 files changed, 135 insertions(+), 158 deletions(-) create mode 100644 tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt delete mode 100644 tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java diff --git a/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt new file mode 100644 index 00000000000..8524ef462fd --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.deviceinfo + +import android.content.Context +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +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 + +@RunWith(AndroidJUnit4::class) +class PhoneNumberPreferenceControllerTest { + + private val mockTelephonyManager = mock() + private val mockSubscriptionManager = mock() + + private val context: Context = + spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(SubscriptionManager::class.java) } doReturn + mockSubscriptionManager + + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + } + + private val subscriptionInfo = mock() + private val preference = spy(Preference(context)) + private val secondPreference = mock() + private var category = PreferenceCategory(context) + private var preferenceScreen = PreferenceManager(context).createPreferenceScreen(context) + + private var controller = spy(PhoneNumberPreferenceController(context, "phone_number")) + + @Before + fun setup() { + preference.setKey(controller.preferenceKey) + preference.isVisible = true + preferenceScreen.addPreference(preference) + category.key = "basic_info_category" + preferenceScreen.addPreference(category) + + doReturn(secondPreference).whenever(controller).createNewPreference(context) + } + + @Test + fun displayPreference_multiSim_shouldAddSecondPreference() { + whenever(mockTelephonyManager.phoneCount).thenReturn(2) + + val sim1Preference = Preference(context) + category.addItemFromInflater(sim1Preference) + controller.displayPreference(preferenceScreen) + + assertThat(category.preferenceCount).isEqualTo(2) + } + + @Test + fun updateState_singleSim_shouldUpdateTitleAndPhoneNumber() { + val phoneNumber = "1111111111" + doReturn(subscriptionInfo).whenever(controller).getSubscriptionInfo(any()) + doReturn(phoneNumber).whenever(controller).getFormattedPhoneNumber(subscriptionInfo) + whenever(mockTelephonyManager.phoneCount).thenReturn(1) + controller.displayPreference(preferenceScreen) + + controller.updateState(preference) + + verify(preference).title = context.getString(R.string.status_number) + verify(preference).summary = phoneNumber + } + + @Test + fun updateState_multiSim_shouldUpdateTitleAndPhoneNumberOfMultiplePreferences() { + val phoneNumber = "1111111111" + doReturn(subscriptionInfo).whenever(controller).getSubscriptionInfo(any()) + doReturn(phoneNumber).whenever(controller).getFormattedPhoneNumber(subscriptionInfo) + whenever(mockTelephonyManager.phoneCount).thenReturn(2) + controller.displayPreference(preferenceScreen) + + controller.updateState(preference) + + verify(preference).title = + context.getString(R.string.status_number_sim_slot, 1 /* sim slot */) + verify(preference).summary = phoneNumber + verify(secondPreference).title = + context.getString(R.string.status_number_sim_slot, 2 /* sim slot */) + verify(secondPreference).summary = phoneNumber + } + + @Test + fun getSummary_cannotGetActiveSubscriptionInfo_shouldShowNotAvailable() { + whenever(mockSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(null) + controller.displayPreference(preferenceScreen) + + controller.updateState(preference) + + verify(preference).summary = context.getString(R.string.device_info_not_available) + } + + @Test + fun getSummary_getEmptySubscriptionInfo_shouldShowNotAvailable() { + whenever(mockSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(emptyList()) + controller.displayPreference(preferenceScreen) + + controller.updateState(preference) + + verify(preference).summary = context.getString(R.string.device_info_not_available) + } +} diff --git a/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java b/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java deleted file mode 100644 index 05e2f246dbd..00000000000 --- a/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2017 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.deviceinfo; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.os.Looper; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; - -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceManager; -import androidx.preference.PreferenceScreen; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.android.settings.R; -import com.android.settings.testutils.ResourcesUtils; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(AndroidJUnit4.class) -public class PhoneNumberPreferenceControllerTest { - - private Preference mPreference; - @Mock - private Preference mSecondPreference; - @Mock - private TelephonyManager mTelephonyManager; - @Mock - private SubscriptionInfo mSubscriptionInfo; - @Mock - private SubscriptionManager mSubscriptionManager; - private PreferenceCategory mCategory; - private PreferenceScreen mScreen; - - private Context mContext; - private PhoneNumberPreferenceController mController; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mContext = spy(ApplicationProvider.getApplicationContext()); - when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); - when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); - mController = spy(new PhoneNumberPreferenceController(mContext, "phone_number")); - - if (Looper.myLooper() == null) { - Looper.prepare(); - } - final PreferenceManager preferenceManager = new PreferenceManager(mContext); - mScreen = preferenceManager.createPreferenceScreen(mContext); - mPreference = spy(new Preference(mContext)); - mPreference.setKey(mController.getPreferenceKey()); - mPreference.setVisible(true); - mScreen.addPreference(mPreference); - final String categoryKey = "basic_info_category"; - mCategory = new PreferenceCategory(mContext); - mCategory.setKey(categoryKey); - mScreen.addPreference(mCategory); - - doReturn(mSecondPreference).when(mController).createNewPreference(mContext); - } - - @Test - public void displayPreference_multiSim_shouldAddSecondPreference() { - when(mTelephonyManager.getPhoneCount()).thenReturn(2); - - final Preference sim1Preference = new Preference(mContext); - mCategory.addItemFromInflater(sim1Preference); - mController.displayPreference(mScreen); - - assertThat(mCategory.getPreferenceCount()).isEqualTo(2); - } - - @Test - public void updateState_singleSim_shouldUpdateTitleAndPhoneNumber() { - final String phoneNumber = "1111111111"; - doReturn(mSubscriptionInfo).when(mController).getSubscriptionInfo(anyInt()); - doReturn(phoneNumber).when(mController).getFormattedPhoneNumber(mSubscriptionInfo); - when(mTelephonyManager.getPhoneCount()).thenReturn(1); - mController.displayPreference(mScreen); - - mController.updateState(mPreference); - - verify(mPreference).setTitle(ResourcesUtils.getResourcesString(mContext, "status_number")); - verify(mPreference).setSummary(phoneNumber); - } - - @Test - public void updateState_multiSim_shouldUpdateTitleAndPhoneNumberOfMultiplePreferences() { - final String phoneNumber = "1111111111"; - doReturn(mSubscriptionInfo).when(mController).getSubscriptionInfo(anyInt()); - doReturn(phoneNumber).when(mController).getFormattedPhoneNumber(mSubscriptionInfo); - when(mTelephonyManager.getPhoneCount()).thenReturn(2); - mController.displayPreference(mScreen); - - mController.updateState(mPreference); - - verify(mPreference).setTitle(ResourcesUtils.getResourcesString( - mContext, "status_number_sim_slot", 1 /* sim slot */)); - verify(mPreference).setSummary(phoneNumber); - verify(mSecondPreference).setTitle(ResourcesUtils.getResourcesString( - mContext, "status_number_sim_slot", 2 /* sim slot */)); - verify(mSecondPreference).setSummary(phoneNumber); - } - - @Test - public void getSummary_cannotGetActiveSubscriptionInfo_shouldShowNotAvailable() { - when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(null); - mController.displayPreference(mScreen); - - mController.updateState(mPreference); - - verify(mPreference).setSummary(mContext.getString(R.string.device_info_not_available)); - } - - @Test - public void getSummary_getEmptySubscriptionInfo_shouldShowNotAvailable() { - List infos = new ArrayList<>(); - when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(infos); - - mController.displayPreference(mScreen); - mController.updateState(mPreference); - - verify(mPreference).setSummary(mContext.getString(R.string.device_info_not_available)); - } -} From cbe75928d9e322e163f56b85b5f64d60769c8370 Mon Sep 17 00:00:00 2001 From: shaoweishen Date: Wed, 9 Oct 2024 14:31:29 +0000 Subject: [PATCH 09/13] [Physical Keyboard] Add main page for Repeat keys Page markup: https://screenshot.googleplex.com/5xCd2wVVyXajdCB.png Test: atest SettingsRoboTests Bug: 345399212 Flag: com.android.input.flags.keyboard_repeat_keys Change-Id: Ie60f4b6ea4de973644103c0aa0bcdfe9a1e758eb --- res/values/strings.xml | 8 +- res/xml/physical_keyboard_settings.xml | 9 +- res/xml/repeat_key_main_page.xml | 42 +++++ .../KeyboardRepeatKeysController.java | 22 ++- ...rdRepeatKeysDelayPreferenceController.java | 65 +++++++ .../KeyboardRepeatKeysMainFragment.java | 170 ++++++++++++++++++ ...RepeatKeysTimeOutPreferenceController.java | 66 +++++++ .../KeyboardRepeatKeysControllerTest.java | 2 +- ...peatKeysDelayPreferenceControllerTest.java | 86 +++++++++ ...atKeysTimeOutPreferenceControllerTest.java | 88 +++++++++ 10 files changed, 546 insertions(+), 12 deletions(-) create mode 100644 res/xml/repeat_key_main_page.xml create mode 100644 src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceController.java create mode 100644 src/com/android/settings/inputmethod/KeyboardRepeatKeysMainFragment.java create mode 100644 src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index a11acb09586..68931751456 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4705,9 +4705,13 @@ Sticky keys, Bounce keys, Mouse keys - Repeat Keys + Repeat Keys + + Delay before repeat + + Repeat Rate - Hold down a key to repeat its character until the key is released + Hold down a key to repeat its character until the key is released %s layout diff --git a/res/xml/physical_keyboard_settings.xml b/res/xml/physical_keyboard_settings.xml index 5d2c5fcd99b..56914052b28 100644 --- a/res/xml/physical_keyboard_settings.xml +++ b/res/xml/physical_keyboard_settings.xml @@ -33,11 +33,12 @@ android:summary="@string/modifier_keys_settings_summary" android:fragment="com.android.settings.inputmethod.ModifierKeysSettings" /> - + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/inputmethod/KeyboardRepeatKeysController.java b/src/com/android/settings/inputmethod/KeyboardRepeatKeysController.java index a232098f696..28392beb34a 100644 --- a/src/com/android/settings/inputmethod/KeyboardRepeatKeysController.java +++ b/src/com/android/settings/inputmethod/KeyboardRepeatKeysController.java @@ -25,14 +25,20 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.LifecycleObserver; import androidx.preference.PreferenceScreen; -import androidx.preference.SwitchPreferenceCompat; + +import com.android.settingslib.PrimarySwitchPreference; +import com.android.settingslib.widget.MainSwitchPreference; public class KeyboardRepeatKeysController extends InputSettingPreferenceController implements LifecycleObserver { + private static final String KEY_REPEAT_KEY = "physical_keyboard_repeat_keys"; + private static final String KEY_REPEAT_KEY_MAIN_PAGE = "repeat_key_main_switch"; @Nullable - private SwitchPreferenceCompat mSwitchPreferenceCompat; + private PrimarySwitchPreference mPrimarySwitchPreference; + @Nullable + private MainSwitchPreference mMainSwitchPreference; public KeyboardRepeatKeysController(@NonNull Context context, @NonNull String key) { @@ -42,7 +48,11 @@ public class KeyboardRepeatKeysController extends @Override public void displayPreference(@NonNull PreferenceScreen screen) { super.displayPreference(screen); - mSwitchPreferenceCompat = screen.findPreference(getPreferenceKey()); + if (KEY_REPEAT_KEY.equals(getPreferenceKey())) { + mPrimarySwitchPreference = screen.findPreference(getPreferenceKey()); + } else if (KEY_REPEAT_KEY_MAIN_PAGE.equals(getPreferenceKey())) { + mMainSwitchPreference = screen.findPreference(getPreferenceKey()); + } } @Override @@ -63,8 +73,10 @@ public class KeyboardRepeatKeysController extends @Override protected void onInputSettingUpdated() { - if (mSwitchPreferenceCompat != null) { - mSwitchPreferenceCompat.setChecked(InputSettings.isRepeatKeysEnabled(mContext)); + if (mPrimarySwitchPreference != null) { + mPrimarySwitchPreference.setChecked(InputSettings.isRepeatKeysEnabled(mContext)); + } else if (mMainSwitchPreference != null) { + mMainSwitchPreference.setChecked(InputSettings.isRepeatKeysEnabled(mContext)); } } diff --git a/src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceController.java b/src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceController.java new file mode 100644 index 00000000000..bb074fd94f7 --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceController.java @@ -0,0 +1,65 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.inputmethod; + +import android.content.Context; +import android.hardware.input.InputSettings; + +import androidx.annotation.NonNull; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.core.SliderPreferenceController; + +import com.google.common.collect.ImmutableList; + +public class KeyboardRepeatKeysDelayPreferenceController extends SliderPreferenceController { + @VisibleForTesting + static final ImmutableList REPEAT_KEY_DELAY_VALUE_LIST = ImmutableList.of(2000, 1000, + 500, 300, 200, 100, 50, 30, 20); + + public KeyboardRepeatKeysDelayPreferenceController(@NonNull Context context, + @NonNull String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getSliderPosition() { + return REPEAT_KEY_DELAY_VALUE_LIST.indexOf(InputSettings.getRepeatKeysDelay(mContext)); + } + + @Override + public boolean setSliderPosition(int position) { + InputSettings.setRepeatKeysDelay(mContext, REPEAT_KEY_DELAY_VALUE_LIST.get(position)); + return true; + } + + @Override + public int getMax() { + return REPEAT_KEY_DELAY_VALUE_LIST.size() - 1; + } + + @Override + public int getMin() { + return 0; + } + + @Override + public int getAvailabilityStatus() { + return InputSettings.isRepeatKeysFeatureFlagEnabled() + ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } +} diff --git a/src/com/android/settings/inputmethod/KeyboardRepeatKeysMainFragment.java b/src/com/android/settings/inputmethod/KeyboardRepeatKeysMainFragment.java new file mode 100644 index 00000000000..39e605d1d59 --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardRepeatKeysMainFragment.java @@ -0,0 +1,170 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.inputmethod; + +import static com.android.settings.inputmethod.PhysicalKeyboardFragment.getHardKeyboards; + +import android.app.settings.SettingsEnums; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.hardware.input.InputManager; +import android.hardware.input.InputSettings; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.util.Preconditions; +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.keyboard.Flags; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.widget.LabeledSeekBarPreference; +import com.android.settingslib.utils.ThreadUtils; + +import java.util.List; + +public class KeyboardRepeatKeysMainFragment extends DashboardFragment + implements InputManager.InputDeviceListener { + private static final String TAG = "RepeatKeysMainFragment"; + private static final String TIME_OUT_KEY = "repeat_keys_timeout_preference"; + private static final String DELAY_KEY = "repeat_keys_delay_preference"; + + private final Uri mRepeatKeyUri = Settings.Secure.getUriFor( + Settings.Secure.KEY_REPEAT_ENABLED); + private final ContentObserver mContentObserver = new ContentObserver(new Handler(true)) { + @Override + public void onChange(boolean selfChange, Uri uri) { + if (mRepeatKeyUri.equals(uri)) { + updatePreferencesState(); + } + } + }; + private InputManager mInputManager; + private ContentResolver mContentResolver; + @Nullable + private LabeledSeekBarPreference mRepeatTimeoutPreference; + @Nullable + private LabeledSeekBarPreference mRepeatDelayPreference; + + @Override + public int getMetricsCategory() { + return SettingsEnums.PHYSICAL_KEYBOARD_A11Y; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mInputManager = Preconditions.checkNotNull(getActivity() + .getSystemService(InputManager.class)); + mContentResolver = context.getContentResolver(); + } + + @Override + public void onCreatePreferences(Bundle bundle, String s) { + super.onCreatePreferences(bundle, s); + mRepeatTimeoutPreference = findPreference(TIME_OUT_KEY); + mRepeatDelayPreference = findPreference(DELAY_KEY); + updatePreferencesState(); + } + + @Override + public void onResume() { + super.onResume(); + finishEarlyIfNeeded(); + mInputManager.registerInputDeviceListener(this, null); + registerSettingsObserver(); + } + + @Override + public void onPause() { + super.onPause(); + mInputManager.unregisterInputDeviceListener(this); + unregisterSettingsObserver(); + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.repeat_key_main_page; + } + + private void updatePreferencesState() { + boolean isRepeatKeyEnabled = InputSettings.isRepeatKeysEnabled(getContext()); + if (mRepeatTimeoutPreference != null && mRepeatDelayPreference != null) { + mRepeatTimeoutPreference.setEnabled(isRepeatKeyEnabled); + mRepeatDelayPreference.setEnabled(isRepeatKeyEnabled); + } + } + + private void registerSettingsObserver() { + unregisterSettingsObserver(); + mContentResolver.registerContentObserver( + mRepeatKeyUri, + false, + mContentObserver, + UserHandle.myUserId()); + } + + private void unregisterSettingsObserver() { + mContentResolver.unregisterContentObserver(mContentObserver); + } + + @Override + public void onInputDeviceAdded(int deviceId) { + finishEarlyIfNeeded(); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + finishEarlyIfNeeded(); + } + + @Override + public void onInputDeviceChanged(int deviceId) { + finishEarlyIfNeeded(); + } + + private void finishEarlyIfNeeded() { + final Context context = getContext(); + ThreadUtils.postOnBackgroundThread(() -> { + final List newHardKeyboards = + getHardKeyboards(context); + if (newHardKeyboards.isEmpty()) { + getActivity().finish(); + } + }); + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.repeat_key_main_page) { + @Override + protected boolean isPageSearchEnabled(Context context) { + return Flags.keyboardAndTouchpadA11yNewPageEnabled() + && !getHardKeyboards(context).isEmpty(); + } + }; +} diff --git a/src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceController.java b/src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceController.java new file mode 100644 index 00000000000..e766d48318e --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceController.java @@ -0,0 +1,66 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.inputmethod; + +import android.content.Context; +import android.hardware.input.InputSettings; + +import androidx.annotation.NonNull; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.core.SliderPreferenceController; + +import com.google.common.collect.ImmutableList; + +public class KeyboardRepeatKeysTimeOutPreferenceController extends SliderPreferenceController { + @VisibleForTesting + static final ImmutableList REPEAT_KEY_TIMEOUT_VALUE_LIST = ImmutableList.of(2000, 1500, + 1000, 400, 300, 200, 150); + + public KeyboardRepeatKeysTimeOutPreferenceController( + @NonNull Context context, + @NonNull String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getSliderPosition() { + return REPEAT_KEY_TIMEOUT_VALUE_LIST.indexOf(InputSettings.getRepeatKeysTimeout(mContext)); + } + + @Override + public boolean setSliderPosition(int position) { + InputSettings.setRepeatKeysTimeout(mContext, REPEAT_KEY_TIMEOUT_VALUE_LIST.get(position)); + return true; + } + + @Override + public int getMax() { + return REPEAT_KEY_TIMEOUT_VALUE_LIST.size() - 1; + } + + @Override + public int getMin() { + return 0; + } + + @Override + public int getAvailabilityStatus() { + return InputSettings.isRepeatKeysFeatureFlagEnabled() + ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } +} diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysControllerTest.java index e1b4ffdc068..d511edcf4ee 100644 --- a/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysControllerTest.java +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysControllerTest.java @@ -51,7 +51,7 @@ public class KeyboardRepeatKeysControllerTest { public void setUp() { mContext = RuntimeEnvironment.application; mKeyboardRepeatKeysController = new KeyboardRepeatKeysController(mContext, - "physical_keyboard_repeat_key"); + "physical_keyboard_repeat_keys"); } @Test diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceControllerTest.java new file mode 100644 index 00000000000..0f2deacbcdd --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysDelayPreferenceControllerTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.inputmethod; + +import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.hardware.input.InputSettings; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + +import com.android.settings.core.BasePreferenceController; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = { + com.android.settings.testutils.shadow.ShadowFragment.class, +}) +public class KeyboardRepeatKeysDelayPreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private Context mContext; + private KeyboardRepeatKeysDelayPreferenceController mRepeatKeysDelayPreferenceController; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mRepeatKeysDelayPreferenceController = new KeyboardRepeatKeysDelayPreferenceController( + mContext, "repeat_keys_delay_preference"); + } + + @Test + @EnableFlags(FLAG_KEYBOARD_REPEAT_KEYS) + public void getAvailabilityStatus_flagIsEnabled_isAvailable() { + assertThat(mRepeatKeysDelayPreferenceController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } + + @Test + @DisableFlags(FLAG_KEYBOARD_REPEAT_KEYS) + public void getAvailabilityStatus_flagIsDisabled_notSupport() { + assertThat(mRepeatKeysDelayPreferenceController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE); + } + + @Test + public void setSliderPosition_updatesInputSettingValue() { + int sliderPosition = 1; + mRepeatKeysDelayPreferenceController.setSliderPosition(sliderPosition); + assertThat(InputSettings.getRepeatKeysDelay(mContext)).isEqualTo( + KeyboardRepeatKeysDelayPreferenceController.REPEAT_KEY_DELAY_VALUE_LIST.get( + sliderPosition)); + } + + @Test + public void getSliderPosition_matchesWithDelayValue() { + int timeout = InputSettings.getRepeatKeysDelay(mContext); + assertThat(mRepeatKeysDelayPreferenceController.getSliderPosition()).isEqualTo( + KeyboardRepeatKeysDelayPreferenceController.REPEAT_KEY_DELAY_VALUE_LIST.indexOf( + timeout)); + } +} diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceControllerTest.java new file mode 100644 index 00000000000..f192c0ca5ed --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardRepeatKeysTimeOutPreferenceControllerTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.inputmethod; + +import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.hardware.input.InputSettings; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + +import com.android.settings.core.BasePreferenceController; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = { + com.android.settings.testutils.shadow.ShadowFragment.class, +}) +public class KeyboardRepeatKeysTimeOutPreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private Context mContext; + private KeyboardRepeatKeysTimeOutPreferenceController + mKeyboardRepeatKeysTimeOutPreferenceController; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mKeyboardRepeatKeysTimeOutPreferenceController = + new KeyboardRepeatKeysTimeOutPreferenceController(mContext, + "repeat_keys_timeout_preference"); + } + + @Test + @EnableFlags(FLAG_KEYBOARD_REPEAT_KEYS) + public void getAvailabilityStatus_flagIsEnabled_isAvailable() { + assertThat(mKeyboardRepeatKeysTimeOutPreferenceController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } + + @Test + @DisableFlags(FLAG_KEYBOARD_REPEAT_KEYS) + public void getAvailabilityStatus_flagIsDisabled_notSupport() { + assertThat(mKeyboardRepeatKeysTimeOutPreferenceController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE); + } + + @Test + public void setSliderPosition_updatesInputSettingValue() { + int sliderPosition = 1; + mKeyboardRepeatKeysTimeOutPreferenceController.setSliderPosition(sliderPosition); + assertThat(InputSettings.getRepeatKeysTimeout(mContext)).isEqualTo( + KeyboardRepeatKeysTimeOutPreferenceController.REPEAT_KEY_TIMEOUT_VALUE_LIST.get( + sliderPosition)); + } + + @Test + public void getSliderPosition_matchesWithTimeoutValue() { + int timeout = InputSettings.getRepeatKeysTimeout(mContext); + assertThat(mKeyboardRepeatKeysTimeOutPreferenceController.getSliderPosition()).isEqualTo( + KeyboardRepeatKeysTimeOutPreferenceController.REPEAT_KEY_TIMEOUT_VALUE_LIST.indexOf( + timeout)); + } +} From 8cb8aaf36b6532a10f8465f584ead54d8e68b46c Mon Sep 17 00:00:00 2001 From: Haijie Hong Date: Mon, 14 Oct 2024 17:19:57 +0800 Subject: [PATCH 10/13] Fix bug when bluetooth profile is not in more settings BUG: 343317785 Test: local tested Flag: com.android.settings.flags.enable_bluetooth_device_details_polish Change-Id: I3f53c3b0b60997aa3a1685fe2512ff2f37b32d18 --- .../bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt index 5418a04d10b..de2d8644877 100644 --- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt +++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt @@ -139,7 +139,7 @@ class DeviceDetailsFragmentFormatterImpl( viewModel .getItems(fragmentType) ?.filterIsInstance() - ?.first() + ?.firstOrNull() ?.invisibleProfiles } From c9b450734a0081d6730444b22d65be9fbfe50772 Mon Sep 17 00:00:00 2001 From: Haijie Hong Date: Mon, 14 Oct 2024 17:40:20 +0800 Subject: [PATCH 11/13] Gray out toggle if isAllowChangingState is false BUG: 343317785 Test: local tested Flag: com.android.settings.flags.enable_bluetooth_device_details_polish Change-Id: I428b5093477ec9d1ba61cbbdb8430b9e28577d99 --- .../settings/bluetooth/ui/model/DeviceSettingPreferenceModel.kt | 1 + .../settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt | 1 + .../bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt | 1 + 3 files changed, 3 insertions(+) diff --git a/src/com/android/settings/bluetooth/ui/model/DeviceSettingPreferenceModel.kt b/src/com/android/settings/bluetooth/ui/model/DeviceSettingPreferenceModel.kt index ba6d1a6a4fb..f4992dafc5e 100644 --- a/src/com/android/settings/bluetooth/ui/model/DeviceSettingPreferenceModel.kt +++ b/src/com/android/settings/bluetooth/ui/model/DeviceSettingPreferenceModel.kt @@ -44,6 +44,7 @@ sealed interface DeviceSettingPreferenceModel { val icon: DeviceSettingIcon? = null, val checked: Boolean, val onCheckedChange: ((Boolean) -> Unit), + val disabled: Boolean = false, val action: DeviceSettingActionModel? = null, ) : DeviceSettingPreferenceModel diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt index de2d8644877..ad4176fc1a2 100644 --- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt +++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt @@ -306,6 +306,7 @@ class DeviceDetailsFragmentFormatterImpl( override val onCheckedChange = { newChecked: Boolean -> model.onCheckedChange(newChecked) } + override val changeable = { !model.disabled } override val icon: (@Composable () -> Unit)? get() { if (model.icon == null) { diff --git a/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt b/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt index a9444a57182..dd0012e90fb 100644 --- a/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt +++ b/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt @@ -101,6 +101,7 @@ class BluetoothDeviceDetailsViewModel( DeviceSettingStateModel.ActionSwitchPreferenceState(newState) ) }, + disabled = !isAllowedChangingState, action = action, ) } else { From 92354f8bed4f912398432c82f6ebecfffa18b869 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Mon, 14 Oct 2024 10:21:38 +0800 Subject: [PATCH 12/13] [Catalyst] Add settings service Bug: 372981584 Flag: com.android.settings.flags.catalyst_service Test: Manual Change-Id: I0bb3752e65e879bd831b7d8c83ea160be32a893e --- Android.bp | 1 + AndroidManifest.xml | 11 ++++++++ aconfig/settings_flag_declarations.aconfig | 7 +++++ src/com/android/settings/SettingsService.kt | 30 +++++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 src/com/android/settings/SettingsService.kt diff --git a/Android.bp b/Android.bp index 28c3148cf28..575f0513273 100644 --- a/Android.bp +++ b/Android.bp @@ -98,6 +98,7 @@ android_library { "SettingsLibDataStore", "SettingsLibMetadata", "SettingsLibPreference", + "SettingsLibService", "aconfig_settings_flags_lib", "accessibility_settings_flags_lib", "contextualcards", diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6408ab11311..0025abaf9e0 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -5299,6 +5299,17 @@ android:enabled="true" android:exported="false" /> + + + + + + + + diff --git a/aconfig/settings_flag_declarations.aconfig b/aconfig/settings_flag_declarations.aconfig index a9c7bd59f23..6eb1e0236de 100644 --- a/aconfig/settings_flag_declarations.aconfig +++ b/aconfig/settings_flag_declarations.aconfig @@ -63,3 +63,10 @@ flag { description: "Flag for all screens" bug: "323791114" } + +flag { + name: "catalyst_service" + namespace: "android_settings" + description: "Flag for catalyst service" + bug: "323791114" +} diff --git a/src/com/android/settings/SettingsService.kt b/src/com/android/settings/SettingsService.kt new file mode 100644 index 00000000000..2cd706b9b14 --- /dev/null +++ b/src/com/android/settings/SettingsService.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings + +import android.content.Intent +import android.os.IBinder +import com.android.settings.flags.Flags +import com.android.settingslib.service.PreferenceService + +/** Service to expose settings APIs. */ +class SettingsService : PreferenceService({ _, _, _ -> true }) { + + override fun onBind(intent: Intent): IBinder? { + return if (!Flags.catalystService()) null else super.onBind(intent) + } +} From eafda3c4903ad0f037721c243d72bd6345015803 Mon Sep 17 00:00:00 2001 From: "Chaitanya Cheemala (xWF)" Date: Mon, 14 Oct 2024 14:18:07 +0000 Subject: [PATCH 13/13] Revert "[Catalyst] Add settings service" Revert submission 29815608-catalyst Reason for revert: Likely culprit for b/373339105 - verifying through ABTD before revert submission. This is part of the standard investigation process, and does not mean your CL will be reverted. Reverted changes: /q/submissionid:29815608-catalyst Change-Id: If42ddb09c07da5672c3d55c2517b75647ea77ae5 --- Android.bp | 1 - AndroidManifest.xml | 11 -------- aconfig/settings_flag_declarations.aconfig | 7 ----- src/com/android/settings/SettingsService.kt | 30 --------------------- 4 files changed, 49 deletions(-) delete mode 100644 src/com/android/settings/SettingsService.kt diff --git a/Android.bp b/Android.bp index 575f0513273..28c3148cf28 100644 --- a/Android.bp +++ b/Android.bp @@ -98,7 +98,6 @@ android_library { "SettingsLibDataStore", "SettingsLibMetadata", "SettingsLibPreference", - "SettingsLibService", "aconfig_settings_flags_lib", "accessibility_settings_flags_lib", "contextualcards", diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0025abaf9e0..6408ab11311 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -5299,17 +5299,6 @@ android:enabled="true" android:exported="false" /> - - - - - - - - diff --git a/aconfig/settings_flag_declarations.aconfig b/aconfig/settings_flag_declarations.aconfig index 6eb1e0236de..a9c7bd59f23 100644 --- a/aconfig/settings_flag_declarations.aconfig +++ b/aconfig/settings_flag_declarations.aconfig @@ -63,10 +63,3 @@ flag { description: "Flag for all screens" bug: "323791114" } - -flag { - name: "catalyst_service" - namespace: "android_settings" - description: "Flag for catalyst service" - bug: "323791114" -} diff --git a/src/com/android/settings/SettingsService.kt b/src/com/android/settings/SettingsService.kt deleted file mode 100644 index 2cd706b9b14..00000000000 --- a/src/com/android/settings/SettingsService.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings - -import android.content.Intent -import android.os.IBinder -import com.android.settings.flags.Flags -import com.android.settingslib.service.PreferenceService - -/** Service to expose settings APIs. */ -class SettingsService : PreferenceService({ _, _, _ -> true }) { - - override fun onBind(intent: Intent): IBinder? { - return if (!Flags.catalystService()) null else super.onBind(intent) - } -}