From 92e23acebbccad87d3c14d0ee981d813da99980c Mon Sep 17 00:00:00 2001 From: Jimi Chen Date: Mon, 2 Jun 2025 13:25:24 +0000 Subject: [PATCH] Introduce carrier-aware avoid bad Wi-Fi APIs This commit introduces new system APIs to manage "avoid bad Wi-Fi" settings, leveraging both global system settings and carrier configurations. This enhancement allows for more granular control and visibility into how Wi-Fi networks are managed, especially in multi-SIM environments. The new `setNetworkAvoidBadWifi(Context, int subId)` method provides the ability to programmatically control this setting based on carrier-specific policies or user preferences. This complements `getNetworkAvoidBadWifi(Context, int subId)`, which allows querying the setting for a specific subscription ID, ensuring the system can accurately determine the avoid bad Wi-Fi status. Additionally, `shouldShowAvoidBadWifiToggle(Context, int subId)` is introduced. This method determines whether the 'Avoid Bad Wi-Fi' user interface toggle should be displayed. It considers both global settings and carrier configurations, allowing for dynamic UI adjustments based on carrier requirements or user preferences. These changes provide greater flexibility for managing Wi-Fi network selection and improve the user experience by offering more context-aware control over cellular behavior. Deprecated APIs: - `setNetworkAvoidBadWifi(Context, int value)` - `getNetworkAvoidBadWifi(Context)` New APIs: - `setNetworkAvoidBadWifi(Context, int subId, int value)` - `getNetworkAvoidBadWifi(Context, int subId)` - `shouldShowAvoidBadWifiToggle(Context, int subId)` Flag: com.android.net.flags.carrier_aware_avoid_bad_wifi Bug: 410976315 Test: atest ConnectivityCoverageTests:android.net.connectivity.android.net.ConnectivitySettingsManagerTest Change-Id: I79469cfe865a585593219d226c8198206c06b589 --- common/flags.aconfig | 8 + framework/Android.bp | 1 + framework/api/module-lib-current.txt | 7 +- .../net/ConnectivitySettingsManager.java | 205 ++++++- .../util/ConnectivitySettingsUtils.java | 140 +++++ .../net/ConnectivitySettingsManagerTest.kt | 520 +++++++++++++++--- 6 files changed, 809 insertions(+), 72 deletions(-) diff --git a/common/flags.aconfig b/common/flags.aconfig index 9905a5e27b..942d079666 100644 --- a/common/flags.aconfig +++ b/common/flags.aconfig @@ -208,3 +208,11 @@ flag { bug: "419123367" is_fixed_read_only: true } + +flag { + name: "carrier_aware_avoid_bad_wifi" + namespace: "android_core_networking" + description: "Flag for API to provide carrier-aware avoid bad Wi-Fi setting." + bug: "410976315" + is_fixed_read_only: true +} diff --git a/framework/Android.bp b/framework/Android.bp index ab3af9a6be..afecb10a1d 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -90,6 +90,7 @@ java_defaults { ], static_libs: [ "com.android.net.flags-aconfig-java", + "android.net.platform.flags-aconfig-java-export", // Not using the latest stable version because all functions in the latest version of // mdns_aidl_interface are deprecated. "mdns_aidl_interface-V1-java", diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 0129e5c73d..c7de146c17 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -93,7 +93,8 @@ package android.net { method @NonNull public static java.time.Duration getMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); method public static boolean getMobileDataAlwaysOn(@NonNull android.content.Context, boolean); method @NonNull public static java.util.Set getMobileDataPreferredUids(@NonNull android.content.Context); - method public static int getNetworkAvoidBadWifi(@NonNull android.content.Context); + method @Deprecated @FlaggedApi("com.android.net.flags.carrier_aware_avoid_bad_wifi") public static int getNetworkAvoidBadWifi(@NonNull android.content.Context); + method @FlaggedApi("com.android.net.flags.carrier_aware_avoid_bad_wifi") public static boolean getNetworkAvoidBadWifi(@NonNull android.content.Context, int); method @Nullable public static String getNetworkMeteredMultipathPreference(@NonNull android.content.Context); method public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, int); method @NonNull public static java.time.Duration getNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration); @@ -114,7 +115,8 @@ package android.net { method public static void setMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); method public static void setMobileDataAlwaysOn(@NonNull android.content.Context, boolean); method public static void setMobileDataPreferredUids(@NonNull android.content.Context, @NonNull java.util.Set); - method public static void setNetworkAvoidBadWifi(@NonNull android.content.Context, int); + method @Deprecated @FlaggedApi("com.android.net.flags.carrier_aware_avoid_bad_wifi") public static void setNetworkAvoidBadWifi(@NonNull android.content.Context, int); + method @FlaggedApi("com.android.net.flags.carrier_aware_avoid_bad_wifi") public static void setNetworkAvoidBadWifi(@NonNull android.content.Context, int, int); method public static void setNetworkMeteredMultipathPreference(@NonNull android.content.Context, @NonNull String); method public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, @IntRange(from=0) int); method public static void setNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration); @@ -124,6 +126,7 @@ package android.net { method public static void setUidsAllowedOnRestrictedNetworks(@NonNull android.content.Context, @NonNull java.util.Set); method public static void setWifiAlwaysRequested(@NonNull android.content.Context, boolean); method public static void setWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); + method @FlaggedApi("com.android.net.flags.carrier_aware_avoid_bad_wifi") public static boolean shouldShowAvoidBadWifiToggle(@NonNull android.content.Context, int); field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2 field public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; // 0x0 field public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; // 0x1 diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java index ba7df7ff9b..502c55d83b 100644 --- a/framework/src/android/net/ConnectivitySettingsManager.java +++ b/framework/src/android/net/ConnectivitySettingsManager.java @@ -20,8 +20,15 @@ import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER; import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE; import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY; +import static com.android.net.module.util.ConnectivitySettingsUtils.getNetworkLegacyGlobalAvoidBadWifiSetting; +import static com.android.net.module.util.ConnectivitySettingsUtils.getNetworkAvoidBadWifiSetting; +import static com.android.net.module.util.ConnectivitySettingsUtils.convertCarrierAwareSettingsStringToMap; import static com.android.net.module.util.ConnectivitySettingsUtils.getPrivateDnsModeAsString; +import static com.android.net.module.util.ConnectivitySettingsUtils.setNetworkLegacyGlobalAvoidBadWifiSetting; +import static com.android.net.module.util.ConnectivitySettingsUtils.setNetworkAvoidBadWifiSetting; +import static com.android.net.module.util.ConnectivitySettingsUtils.convertCarrierAwareSettingsMapToString; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -32,14 +39,17 @@ import android.content.pm.PackageManager; import android.net.ConnectivityManager.MultipathPreference; import android.os.Binder; import android.os.Build; +import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; import android.provider.Settings; +import android.telephony.CarrierConfigManager; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.Range; +import com.android.net.flags.Flags; import com.android.net.module.util.ConnectivitySettingsUtils; import com.android.net.module.util.ProxyUtils; @@ -47,6 +57,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.time.Duration; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.StringJoiner; @@ -61,6 +72,29 @@ public class ConnectivitySettingsManager { private ConnectivitySettingsManager() {} + private static boolean isCarrierConfigValid(@Nullable PersistableBundle config) { + return config != null && !config.isEmpty() + && CarrierConfigManager.isConfigForIdentifiedCarrier(config); + } + + private static @NonNull PersistableBundle getConfigForSubId( + @NonNull CarrierConfigManager ccm, int subId, @NonNull String... keys) { + PersistableBundle config = null; + try { + config = ccm.getConfigForSubId(subId, keys); + } catch (RuntimeException e) { + Log.e(TAG, "Failed to get config from carrier config", e); + } + + if (!isCarrierConfigValid(config)) { + // If the "avoid bad Wi-Fi" setting is not defined in the carrier-specific config, + // retrieve the default configuration. + config = CarrierConfigManager.getDefaultConfig(); + } + + return config; + } + /** Data activity timeout settings */ /** @@ -324,6 +358,32 @@ public class ConnectivitySettingsManager { */ public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2; + /** + * Per-carrier setting whether to switch away from wifi networks that lose access. + * + * When a device is connected to both mobile and wifi, and wifi loses Internet + * access, normally the device will switch to the mobile network. + * Carrier config can override this default behavior per-carrier. + * This setting, if defined, overrides both the default and carrier config + * for that particular carrier. + * + * The format is a semicolon-separated list of `subId,value` pairs, where: + * + * subId: The carrier subscription ID (integer). + * value: Controls the avoidance behavior for the corresponding `subId`. + * + * Valid values are: + * 0: Don't avoid bad Wi-Fi for this subscription, and don't prompt the user. + * 1: Avoid bad Wi-Fi for this subscription. + * + * For example: "1,1;2,0" would mean subId 1 avoids bad Wi-Fi, and subId 2 does not. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_CARRIER_AWARE_AVOID_BAD_WIFI) + public static final String NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI = + "network_carrier_aware_avoid_bad_wifi"; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { @@ -931,11 +991,13 @@ public class ConnectivitySettingsManager { * @param context The {@link Context} to query the setting. * @return The setting whether to automatically switch away from wifi networks that lose * internet access. + * @deprecated Use {@link #getNetworkAvoidBadWifi(Context, int)} instead. */ + @FlaggedApi(Flags.FLAG_CARRIER_AWARE_AVOID_BAD_WIFI) + @Deprecated @NetworkAvoidBadWifi public static int getNetworkAvoidBadWifi(@NonNull Context context) { - final String setting = - Settings.Global.getString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI); + final String setting = getNetworkLegacyGlobalAvoidBadWifiSetting(context); if ("0".equals(setting)) { return NETWORK_AVOID_BAD_WIFI_IGNORE; } else if ("1".equals(setting)) { @@ -951,7 +1013,10 @@ public class ConnectivitySettingsManager { * @param context The {@link Context} to set the setting. * @param value Whether to automatically switch away from wifi networks that lose internet * access. + * @deprecated Use {@link #setNetworkAvoidBadWifi(Context, int, int)} instead. */ + @FlaggedApi(Flags.FLAG_CARRIER_AWARE_AVOID_BAD_WIFI) + @Deprecated public static void setNetworkAvoidBadWifi(@NonNull Context context, @NetworkAvoidBadWifi int value) { final String setting; @@ -964,7 +1029,141 @@ public class ConnectivitySettingsManager { } else { throw new IllegalArgumentException("Invalid avoid bad wifi setting"); } - Settings.Global.putString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI, setting); + setNetworkLegacyGlobalAvoidBadWifiSetting(context, setting); + } + + /** + * Set avoid bad wifi setting to {@link Settings} with specific subscription ID. + * + * @param context The {@link Context} to set the setting. + * @param subId The subscription ID to query carrier configurations for. + * @param value Whether to automatically switch away from wifi networks that lose internet + * access. + */ + @FlaggedApi(Flags.FLAG_CARRIER_AWARE_AVOID_BAD_WIFI) + public static void setNetworkAvoidBadWifi(@NonNull Context context, int subId, + @NetworkAvoidBadWifi int value) { + final String setting = getNetworkAvoidBadWifiSetting(context); + final Map settingMap = + convertCarrierAwareSettingsStringToMap(setting); + + if (value == NETWORK_AVOID_BAD_WIFI_IGNORE) { + settingMap.put(subId, false); + } else if (value == NETWORK_AVOID_BAD_WIFI_AVOID) { + settingMap.put(subId, true); + } else if (value == NETWORK_AVOID_BAD_WIFI_PROMPT) { + settingMap.remove(subId); + } else { + throw new IllegalArgumentException("Invalid avoid bad wifi setting"); + } + + if (settingMap.isEmpty()) { + setNetworkAvoidBadWifiSetting(context, null); + } else { + setNetworkAvoidBadWifiSetting( + context, + convertCarrierAwareSettingsMapToString(context, settingMap) + ); + } + + // for backward compatibility, write to the old avoid bad wifi settings as well. + if (settingMap.containsKey(subId)) { + setNetworkLegacyGlobalAvoidBadWifiSetting(context, settingMap.get(subId) ? "1" : "0"); + } else { + setNetworkLegacyGlobalAvoidBadWifiSetting(context, null); + } + } + + /** + * Get avoid bad wifi setting from {@link Settings} with specific subscription ID. + * + * @param context The {@link Context} to query the setting and system services. + * @param subId The subscription ID to query carrier configurations for. + * @return The setting whether to automatically switch away from wifi networks that lose + * internet access. + */ + @FlaggedApi(Flags.FLAG_CARRIER_AWARE_AVOID_BAD_WIFI) + public static boolean getNetworkAvoidBadWifi(@NonNull Context context, int subId) { + final String setting = + getNetworkAvoidBadWifiSetting(context); + final boolean defaultConfig = true; + // check new avoid bad wifi setting first if it's set + if (setting != null) { + final Map settingMap = + convertCarrierAwareSettingsStringToMap(setting); + if (settingMap.containsKey(subId)) { + return settingMap.get(subId); + } + } + + // for backward compatibility, read the old avoid bad wifi settings if new key is not set. + final String oldSetting = getNetworkLegacyGlobalAvoidBadWifiSetting(context); + if (oldSetting != null) { + if (oldSetting.equals("1")) { + return true; + } else if (oldSetting.equals("0")) { + return false; + } + } + + // read carrier config if both new and old settings is not set. + final CarrierConfigManager ccm = context.getSystemService(CarrierConfigManager.class); + if (ccm == null) return defaultConfig; + + final PersistableBundle config = + getConfigForSubId(ccm, subId, CarrierConfigManager.KEY_AVOID_BAD_WIFI_BOOL); + + return config.getBoolean(CarrierConfigManager.KEY_AVOID_BAD_WIFI_BOOL, defaultConfig); + } + + /** + * Determines whether the "Avoid Bad Wi-Fi" toggle should be displayed to the user. + * + * This method checks both global system settings and carrier configurations to decide + * if the option for automatically switching away from Wi-Fi networks with no internet + * access should be visible in the user interface. + * + * @param context The {@link Context} to query settings and system services. + * @param subId The subscription ID to query carrier configurations for. + * @return {@code true} if the "Avoid Bad Wi-Fi" toggle should be shown; + * {@code false} otherwise. + */ + @FlaggedApi(Flags.FLAG_CARRIER_AWARE_AVOID_BAD_WIFI) + public static boolean shouldShowAvoidBadWifiToggle(@NonNull Context context, int subId) { + final String setting = + getNetworkAvoidBadWifiSetting(context); + final CarrierConfigManager ccm = context.getSystemService(CarrierConfigManager.class); + final boolean defaultConfig = true; + final boolean defaultDisplay = false; + + // check new avoid bad wifi setting first if it's set + if (setting != null) return true; + + // for backward compatibility, read the old avoid bad wifi if new setting is not set. + final String oldSetting = getNetworkLegacyGlobalAvoidBadWifiSetting(context); + if (oldSetting != null) return true; + + // default to false if system does not support CarrierConfigManager + if (ccm == null) return defaultDisplay; + + final PersistableBundle config = getConfigForSubId( + ccm, + subId, + CarrierConfigManager.KEY_AVOID_BAD_WIFI_BOOL, + CarrierConfigManager.KEY_SHOW_AVOID_BAD_WIFI_TOGGLE_BOOL + ); + + if (!config.getBoolean(CarrierConfigManager.KEY_AVOID_BAD_WIFI_BOOL, defaultConfig)) { + // It should not be possible to force a device to be stuck on bad wifi + // and not give the user an option to change this, so if the device doesn't + // avoid bad wifi, show the toggle even if KEY_SHOW_AVOID_BAD_WIFI_TOGGLE_BOOL + // is false. + return true; + } + + return config.getBoolean( + CarrierConfigManager.KEY_SHOW_AVOID_BAD_WIFI_TOGGLE_BOOL, defaultDisplay); + } /** diff --git a/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java b/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java index f4856b344f..48b6438e5b 100644 --- a/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java +++ b/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java @@ -22,6 +22,10 @@ import android.content.ContentResolver; import android.content.Context; import android.provider.Settings; import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; + +import java.util.Map; /** * Collection of connectivity settings utilities. @@ -29,6 +33,7 @@ import android.text.TextUtils; * @hide */ public class ConnectivitySettingsUtils { + public static final String TAG = ConnectivitySettingsUtils.class.getSimpleName(); public static final int PRIVATE_DNS_MODE_OFF = 1; public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2; public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3; @@ -40,6 +45,10 @@ public class ConnectivitySettingsUtils { public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING = "hostname"; public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier"; + public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi"; + public static final String NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI = + "network_carrier_aware_avoid_bad_wifi"; + /** * Get private DNS mode as string. * @@ -128,4 +137,135 @@ public class ConnectivitySettingsUtils { public static void setPrivateDnsHostname(@NonNull Context context, @Nullable String specifier) { Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER, specifier); } + + /** + * Set legacy global avoid bad wifi to {@link Settings}. + * + * @param context The {@link Context} to set the setting. + * @param setting The desired setting value. + * "0": Don't avoid bad Wi-Fi. + * "1": Avoid bad Wi-Fi. + * {@code null}: Ask the user whether to switch away from bad Wi-Fi. + * @deprecated Use {@link #setNetworkAvoidBadWifiSetting(Context, String)} instead. + */ + @Deprecated + public static void setNetworkLegacyGlobalAvoidBadWifiSetting( + @NonNull Context context, @Nullable String setting) { + Settings.Global.putString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI, setting); + } + + /** + * Get legacy global avoid bad wifi to {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @return The current setting value, which can be "0", "1", or {@code null}. + * Returns {@code null} if the setting is not found. + * @deprecated Use {@link #getNetworkAvoidBadWifiSetting(Context)} instead. + */ + @Deprecated + @Nullable + public static String getNetworkLegacyGlobalAvoidBadWifiSetting(@NonNull Context context) { + return Settings.Global.getString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI); + } + + /** + * Set the carrier-aware avoid bad wifi to {@link Settings}. + * + * @param context The {@link Context} to set the setting. + * @param setting The desired setting string, formatted as "subId1,value1;subId2,value2". + * subId: The carrier subscription ID (integer). + * value: "0" to not avoid bad Wi-Fi for this subscription, or "1" to avoid. + * {@code null}: Ask the user whether to switch away from bad Wi-Fi. + */ + public static void setNetworkAvoidBadWifiSetting( + @NonNull Context context, @Nullable String setting) { + Settings.Global.putString( + context.getContentResolver(), NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI, setting); + } + + /** + * Get the raw carrier-aware avoid bad wifi string from {@link Settings}. + * @see #convertCarrierAwareSettingsStringToMap(String) + * + * @param context The {@link Context} to set the setting. + * @return The current setting string, formatted as "subId1,value1;subId2,value2", + * or {@code null} if the setting is not set. + */ + @Nullable + public static String getNetworkAvoidBadWifiSetting(@NonNull Context context) { + return Settings.Global.getString( + context.getContentResolver(), NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI); + } + + /** + * Parses the carrier-aware Wi-Fi avoid bad wifi setting string into a map. + * + * @param setting The setting string, typically retrieved from + * {@link #getNetworkAvoidBadWifiSetting(Context)}. + * Expected format is "subId1,value1;subId2,value2". + * @return A {@link Map} where keys are carrier subscription IDs ({@link Integer}) + * and values are booleans indicating whether to avoid bad Wi-Fi ({@code true} for avoid, + * {@code false} for not avoid). + * Returns an empty map if the input setting is null or invalid. + */ + @NonNull + public static Map convertCarrierAwareSettingsStringToMap( + @Nullable String setting) { + final ArrayMap settingMap = new ArrayMap<>(); + if (setting == null) { + return settingMap; + } + + for (String entry: setting.split(";")) { + final String[] parts = entry.split(","); + if (parts.length != 2) { + Log.e(TAG, "invalid setting string: " + entry); + continue; + } + + try { + int subId = Integer.parseInt(parts[0].trim()); + boolean value = Integer.parseInt(parts[1].trim()) != 0; + settingMap.put(subId, value); + } catch (NumberFormatException e) { + Log.e(TAG, "invalid setting string " + entry); + } + } + + return settingMap; + } + + /** + * Converts a map of carrier subscription IDs to Wi-Fi avoidance preferences into a string. + * + * @param context The application context. + * @param settingMap A {@link Map} where keys are carrier subscription IDs ({@link Integer}) + * and values are booleans indicating whether to avoid bad Wi-Fi ({@code true} for avoid, + * {@code false} for not avoid). + * This map will be converted into a "subId,value;subId2,value2" string format. + * @return A string representing the carrier-aware Wi-Fi avoidance settings, + * or {@code null} if the provided {@code settingMap} is empty. + * The format is "subId1,value1;subId2,value2". + */ + @Nullable + public static String convertCarrierAwareSettingsMapToString( + @NonNull Context context, @NonNull Map settingMap) { + if (settingMap.isEmpty()) return null; + + final StringBuilder sb = new StringBuilder(); + int index = 0; + + for (Map.Entry entry: settingMap.entrySet()) { + final String valueStr = entry.getValue() ? "1" : "0"; + sb.append(entry.getKey()) + .append(",") + .append(valueStr); + if (index < settingMap.size() - 1) { + sb.append(";"); + } + index++; + } + + return sb.toString(); + } } diff --git a/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt b/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt index d14d127efc..c7ba27e7f4 100644 --- a/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt +++ b/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt @@ -28,6 +28,8 @@ import android.net.ConnectivitySettingsManager.DNS_RESOLVER_MIN_SAMPLES import android.net.ConnectivitySettingsManager.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS import android.net.ConnectivitySettingsManager.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT import android.net.ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON +import android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI +import android.net.ConnectivitySettingsManager.NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI import android.net.ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT import android.net.ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS import android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE @@ -42,6 +44,7 @@ import android.net.ConnectivitySettingsManager.getDnsResolverSuccessThresholdPer import android.net.ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond import android.net.ConnectivitySettingsManager.getMobileDataActivityTimeout import android.net.ConnectivitySettingsManager.getMobileDataAlwaysOn +import android.net.ConnectivitySettingsManager.getNetworkAvoidBadWifi import android.net.ConnectivitySettingsManager.getNetworkSwitchNotificationMaximumDailyCount import android.net.ConnectivitySettingsManager.getNetworkSwitchNotificationRateDuration import android.net.ConnectivitySettingsManager.getPrivateDnsDefaultMode @@ -55,27 +58,42 @@ import android.net.ConnectivitySettingsManager.setDnsResolverSuccessThresholdPer import android.net.ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond import android.net.ConnectivitySettingsManager.setMobileDataActivityTimeout import android.net.ConnectivitySettingsManager.setMobileDataAlwaysOn +import android.net.ConnectivitySettingsManager.setNetworkAvoidBadWifi import android.net.ConnectivitySettingsManager.setNetworkSwitchNotificationMaximumDailyCount import android.net.ConnectivitySettingsManager.setNetworkSwitchNotificationRateDuration import android.net.ConnectivitySettingsManager.setPrivateDnsDefaultMode import android.net.ConnectivitySettingsManager.setWifiAlwaysRequested import android.net.ConnectivitySettingsManager.setWifiDataActivityTimeout +import android.net.ConnectivitySettingsManager.shouldShowAvoidBadWifiToggle import android.os.Build +import android.os.PersistableBundle import android.platform.test.annotations.AppModeFull import android.provider.Settings +import android.telephony.CarrierConfigManager +import android.telephony.SubscriptionManager import android.util.Range import androidx.test.InstrumentationRegistry import androidx.test.filters.SmallTest +import com.android.net.module.util.ConnectivitySettingsUtils.convertCarrierAwareSettingsStringToMap +import com.android.net.module.util.ConnectivitySettingsUtils.getNetworkAvoidBadWifiSetting +import com.android.net.module.util.ConnectivitySettingsUtils.getNetworkLegacyGlobalAvoidBadWifiSetting import com.android.net.module.util.ConnectivitySettingsUtils.getPrivateDnsModeAsString +import com.android.net.module.util.ConnectivitySettingsUtils.setNetworkAvoidBadWifiSetting +import com.android.net.module.util.ConnectivitySettingsUtils.setNetworkLegacyGlobalAvoidBadWifiSetting import com.android.testutils.ConnectivityModuleTest +import com.android.testutils.DevSdkIgnoreRule import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo import com.android.testutils.DevSdkIgnoreRunner -import junit.framework.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith +import com.android.testutils.com.android.testutils.CarrierConfigRule import java.time.Duration import java.util.Objects +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue import kotlin.test.assertFailsWith +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith /** * Tests for [ConnectivitySettingsManager]. @@ -98,12 +116,15 @@ class ConnectivitySettingsManagerTest { private val settingsTypeGlobal = "global" private val settingsTypeSecure = "secure" + private val defaultSubId = SubscriptionManager.getDefaultSubscriptionId() + /*** Reset setting value or delete setting if the setting was not existed before testing. */ private fun resetSettings(names: Array, type: String, values: Array) { for (i in names.indices) { if (Objects.equals(values[i], null)) { instrumentation.uiAutomation.executeShellCommand( - "settings delete $type ${names[i]}") + "settings delete $type ${names[i]}" + ) } else { if (settingsTypeSecure.equals(type)) { Settings.Secure.putString(resolver, names[i], values[i]) @@ -114,6 +135,18 @@ class ConnectivitySettingsManagerTest { } } + private fun assertAvoidBadWifiSettingEquals(expected: String?, setting: String?) { + // The conversion is necessary because the strings may not be in the same order. + assertEquals( + convertCarrierAwareSettingsStringToMap( + expected + ), + convertCarrierAwareSettingsStringToMap( + setting + ) + ) + } + fun testIntSetting( names: Array, type: String, @@ -148,32 +181,67 @@ class ConnectivitySettingsManagerTest { } } + fun overrideAvoidBadWifiCarrierConfig( + avoidBadWifi: Boolean? = null, + showAvoidBadWifiToggle: Boolean? = null + ) { + // Override carrier config to ignore entitlement check. + val bundle = PersistableBundle() + + avoidBadWifi?.let { value -> + bundle.putBoolean(CarrierConfigManager.KEY_AVOID_BAD_WIFI_BOOL, value) + } + + showAvoidBadWifiToggle?.let { value -> + bundle.putBoolean(CarrierConfigManager.KEY_SHOW_AVOID_BAD_WIFI_TOGGLE_BOOL, value) + } + + mCarrierConfigRule.addConfigOverrides(defaultSubId, bundle) + } + + @get:Rule + val mCarrierConfigRule: CarrierConfigRule = CarrierConfigRule() + + @get:Rule + val ignoreRule = DevSdkIgnoreRule() + @Test fun testMobileDataActivityTimeout() { - testIntSetting(names = arrayOf(DATA_ACTIVITY_TIMEOUT_MOBILE), type = settingsTypeGlobal, - value1 = Duration.ofSeconds(testTime1), value2 = Duration.ofSeconds(testTime2), - getter = { getMobileDataActivityTimeout(context, defaultDuration) }, - setter = { setMobileDataActivityTimeout(context, it) }, - testIntValues = intArrayOf(testTime1.toInt())) + testIntSetting( + names = arrayOf(DATA_ACTIVITY_TIMEOUT_MOBILE), + type = settingsTypeGlobal, + value1 = Duration.ofSeconds(testTime1), + value2 = Duration.ofSeconds(testTime2), + getter = { getMobileDataActivityTimeout(context, defaultDuration) }, + setter = { setMobileDataActivityTimeout(context, it) }, + testIntValues = intArrayOf(testTime1.toInt()) + ) } @Test fun testWifiDataActivityTimeout() { - testIntSetting(names = arrayOf(DATA_ACTIVITY_TIMEOUT_WIFI), type = settingsTypeGlobal, - value1 = Duration.ofSeconds(testTime1), value2 = Duration.ofSeconds(testTime2), - getter = { getWifiDataActivityTimeout(context, defaultDuration) }, - setter = { setWifiDataActivityTimeout(context, it) }, - testIntValues = intArrayOf(testTime1.toInt())) + testIntSetting( + names = arrayOf(DATA_ACTIVITY_TIMEOUT_WIFI), + type = settingsTypeGlobal, + value1 = Duration.ofSeconds(testTime1), + value2 = Duration.ofSeconds(testTime2), + getter = { getWifiDataActivityTimeout(context, defaultDuration) }, + setter = { setWifiDataActivityTimeout(context, it) }, + testIntValues = intArrayOf(testTime1.toInt()) + ) } @Test fun testDnsResolverSampleValidityDuration() { - testIntSetting(names = arrayOf(DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS), - type = settingsTypeGlobal, value1 = Duration.ofSeconds(testTime1), - value2 = Duration.ofSeconds(testTime2), - getter = { getDnsResolverSampleValidityDuration(context, defaultDuration) }, - setter = { setDnsResolverSampleValidityDuration(context, it) }, - testIntValues = intArrayOf(testTime1.toInt())) + testIntSetting( + names = arrayOf(DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS), + type = settingsTypeGlobal, + value1 = Duration.ofSeconds(testTime1), + value2 = Duration.ofSeconds(testTime2), + getter = { getDnsResolverSampleValidityDuration(context, defaultDuration) }, + setter = { setDnsResolverSampleValidityDuration(context, it) }, + testIntValues = intArrayOf(testTime1.toInt()) + ) assertFailsWith("Expect fail but argument accepted.") { setDnsResolverSampleValidityDuration(context, Duration.ofSeconds(-1L)) } @@ -181,11 +249,15 @@ class ConnectivitySettingsManagerTest { @Test fun testDnsResolverSuccessThresholdPercent() { - testIntSetting(names = arrayOf(DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT), - type = settingsTypeGlobal, value1 = 5, value2 = 10, - getter = { getDnsResolverSuccessThresholdPercent(context, 0 /* def */) }, - setter = { setDnsResolverSuccessThresholdPercent(context, it) }, - testIntValues = intArrayOf(5)) + testIntSetting( + names = arrayOf(DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT), + type = settingsTypeGlobal, + value1 = 5, + value2 = 10, + getter = { getDnsResolverSuccessThresholdPercent(context, 0 /* def */) }, + setter = { setDnsResolverSuccessThresholdPercent(context, it) }, + testIntValues = intArrayOf(5) + ) assertFailsWith("Expect fail but argument accepted.") { setDnsResolverSuccessThresholdPercent(context, -1) } @@ -195,11 +267,15 @@ class ConnectivitySettingsManagerTest { @Test fun testDnsResolverSampleRanges() { - testIntSetting(names = arrayOf(DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_MAX_SAMPLES), - type = settingsTypeGlobal, value1 = Range(1, 63), value2 = Range(2, 62), - getter = { getDnsResolverSampleRanges(context) }, - setter = { setDnsResolverSampleRanges(context, it) }, - testIntValues = intArrayOf(1, 63)) + testIntSetting( + names = arrayOf(DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_MAX_SAMPLES), + type = settingsTypeGlobal, + value1 = Range(1, 63), + value2 = Range(2, 62), + getter = { getDnsResolverSampleRanges(context) }, + setter = { setDnsResolverSampleRanges(context, it) }, + testIntValues = intArrayOf(1, 63) + ) assertFailsWith("Expect fail but argument accepted.") { setDnsResolverSampleRanges(context, Range(-1, 62)) } @@ -209,11 +285,15 @@ class ConnectivitySettingsManagerTest { @Test fun testNetworkSwitchNotificationMaximumDailyCount() { - testIntSetting(names = arrayOf(NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT), - type = settingsTypeGlobal, value1 = 5, value2 = 15, - getter = { getNetworkSwitchNotificationMaximumDailyCount(context, 0 /* def */) }, - setter = { setNetworkSwitchNotificationMaximumDailyCount(context, it) }, - testIntValues = intArrayOf(5)) + testIntSetting( + names = arrayOf(NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT), + type = settingsTypeGlobal, + value1 = 5, + value2 = 15, + getter = { getNetworkSwitchNotificationMaximumDailyCount(context, 0 /* def */) }, + setter = { setNetworkSwitchNotificationMaximumDailyCount(context, it) }, + testIntValues = intArrayOf(5) + ) assertFailsWith("Expect fail but argument accepted.") { setNetworkSwitchNotificationMaximumDailyCount(context, -1) } @@ -221,12 +301,15 @@ class ConnectivitySettingsManagerTest { @Test fun testNetworkSwitchNotificationRateDuration() { - testIntSetting(names = arrayOf(NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS), - type = settingsTypeGlobal, value1 = Duration.ofMillis(testTime1), - value2 = Duration.ofMillis(testTime2), - getter = { getNetworkSwitchNotificationRateDuration(context, defaultDuration) }, - setter = { setNetworkSwitchNotificationRateDuration(context, it) }, - testIntValues = intArrayOf(testTime1.toInt())) + testIntSetting( + names = arrayOf(NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS), + type = settingsTypeGlobal, + value1 = Duration.ofMillis(testTime1), + value2 = Duration.ofMillis(testTime2), + getter = { getNetworkSwitchNotificationRateDuration(context, defaultDuration) }, + setter = { setNetworkSwitchNotificationRateDuration(context, it) }, + testIntValues = intArrayOf(testTime1.toInt()) + ) assertFailsWith("Expect fail but argument accepted.") { setNetworkSwitchNotificationRateDuration(context, Duration.ofMillis(-1L)) } @@ -234,11 +317,15 @@ class ConnectivitySettingsManagerTest { @Test fun testCaptivePortalMode() { - testIntSetting(names = arrayOf(CAPTIVE_PORTAL_MODE), type = settingsTypeGlobal, - value1 = CAPTIVE_PORTAL_MODE_AVOID, value2 = CAPTIVE_PORTAL_MODE_PROMPT, - getter = { getCaptivePortalMode(context, CAPTIVE_PORTAL_MODE_IGNORE) }, - setter = { setCaptivePortalMode(context, it) }, - testIntValues = intArrayOf(CAPTIVE_PORTAL_MODE_AVOID)) + testIntSetting( + names = arrayOf(CAPTIVE_PORTAL_MODE), + type = settingsTypeGlobal, + value1 = CAPTIVE_PORTAL_MODE_AVOID, + value2 = CAPTIVE_PORTAL_MODE_PROMPT, + getter = { getCaptivePortalMode(context, CAPTIVE_PORTAL_MODE_IGNORE) }, + setter = { setCaptivePortalMode(context, it) }, + testIntValues = intArrayOf(CAPTIVE_PORTAL_MODE_AVOID) + ) assertFailsWith("Expect fail but argument accepted.") { setCaptivePortalMode(context, 5 /* mode */) } @@ -254,11 +341,16 @@ class ConnectivitySettingsManagerTest { assertEquals(mode, getPrivateDnsDefaultMode(context)) setPrivateDnsDefaultMode(context, PRIVATE_DNS_MODE_OFF) - assertEquals(getPrivateDnsModeAsString(PRIVATE_DNS_MODE_OFF), - getPrivateDnsDefaultMode(context)) + assertEquals( + getPrivateDnsModeAsString(PRIVATE_DNS_MODE_OFF), + getPrivateDnsDefaultMode(context) + ) } finally { - resetSettings(names = arrayOf(PRIVATE_DNS_DEFAULT_MODE), type = settingsTypeGlobal, - values = arrayOf(original)) + resetSettings( + names = arrayOf(PRIVATE_DNS_DEFAULT_MODE), + type = settingsTypeGlobal, + values = arrayOf(original) + ) } assertFailsWith("Expect fail but argument accepted.") { @@ -267,12 +359,15 @@ class ConnectivitySettingsManagerTest { @Test fun testConnectivityKeepPendingIntentDuration() { - testIntSetting(names = arrayOf(CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS), - type = settingsTypeSecure, value1 = Duration.ofMillis(testTime1), - value2 = Duration.ofMillis(testTime2), - getter = { getConnectivityKeepPendingIntentDuration(context, defaultDuration) }, - setter = { setConnectivityKeepPendingIntentDuration(context, it) }, - testIntValues = intArrayOf(testTime1.toInt())) + testIntSetting( + names = arrayOf(CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS), + type = settingsTypeSecure, + value1 = Duration.ofMillis(testTime1), + value2 = Duration.ofMillis(testTime2), + getter = { getConnectivityKeepPendingIntentDuration(context, defaultDuration) }, + setter = { setConnectivityKeepPendingIntentDuration(context, it) }, + testIntValues = intArrayOf(testTime1.toInt()) + ) assertFailsWith("Expect fail but argument accepted.") { setConnectivityKeepPendingIntentDuration(context, Duration.ofMillis(-1L)) } @@ -280,20 +375,28 @@ class ConnectivitySettingsManagerTest { @Test fun testMobileDataAlwaysOn() { - testIntSetting(names = arrayOf(MOBILE_DATA_ALWAYS_ON), type = settingsTypeGlobal, - value1 = false, value2 = true, - getter = { getMobileDataAlwaysOn(context, true /* def */) }, - setter = { setMobileDataAlwaysOn(context, it) }, - testIntValues = intArrayOf(0)) + testIntSetting( + names = arrayOf(MOBILE_DATA_ALWAYS_ON), + type = settingsTypeGlobal, + value1 = false, + value2 = true, + getter = { getMobileDataAlwaysOn(context, true /* def */) }, + setter = { setMobileDataAlwaysOn(context, it) }, + testIntValues = intArrayOf(0) + ) } @Test fun testWifiAlwaysRequested() { - testIntSetting(names = arrayOf(WIFI_ALWAYS_REQUESTED), type = settingsTypeGlobal, - value1 = false, value2 = true, - getter = { getWifiAlwaysRequested(context, true /* def */) }, - setter = { setWifiAlwaysRequested(context, it) }, - testIntValues = intArrayOf(0)) + testIntSetting( + names = arrayOf(WIFI_ALWAYS_REQUESTED), + type = settingsTypeGlobal, + value1 = false, + value2 = true, + getter = { getWifiAlwaysRequested(context, true /* def */) }, + setter = { setWifiAlwaysRequested(context, it) }, + testIntValues = intArrayOf(0) + ) } @ConnectivityModuleTest // get/setIngressRateLimitInBytesPerSecond was added via module update @@ -311,4 +414,287 @@ class ConnectivitySettingsManagerTest { setIngressRateLimitInBytesPerSecond(context, -10) } } -} \ No newline at end of file + + @Test + @IgnoreUpTo(Build.VERSION_CODES.BAKLAVA) + fun testSetNetworkAvoidBadWifiWithSettings() { + val orgCarrierAwareSetting = getNetworkAvoidBadWifiSetting(context) + val orgSetting = getNetworkLegacyGlobalAvoidBadWifiSetting(context) + try { + // initialize NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI and NETWORK_AVOID_BAD_WIFI to null + setNetworkAvoidBadWifiSetting(context, null) + setNetworkLegacyGlobalAvoidBadWifiSetting(context, null) + + val testSubId = 1000 + // test with if new key NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI is set. + // format: "subId1,value1;subId2,value2;..." + // and if old key NETWORK_AVOID_BAD_WIFI is set as well. + // format: "value" + setNetworkAvoidBadWifi( + context, + defaultSubId, + ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_AVOID + ) + assertAvoidBadWifiSettingEquals( + "$defaultSubId,1", + getNetworkAvoidBadWifiSetting(context) + ) + assertEquals( + "1", + getNetworkLegacyGlobalAvoidBadWifiSetting(context) + ) + + setNetworkAvoidBadWifi( + context, + defaultSubId, + ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_IGNORE + ) + assertAvoidBadWifiSettingEquals( + "$defaultSubId,0", + getNetworkAvoidBadWifiSetting(context) + ) + assertEquals( + "0", + getNetworkLegacyGlobalAvoidBadWifiSetting(context) + ) + + setNetworkAvoidBadWifi( + context, + testSubId, + ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_AVOID + ) + assertAvoidBadWifiSettingEquals( + "$defaultSubId,0;$testSubId,1", + getNetworkAvoidBadWifiSetting(context) + ) + assertEquals( + "1", + getNetworkLegacyGlobalAvoidBadWifiSetting(context) + ) + + setNetworkAvoidBadWifi( + context, + testSubId, + ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_IGNORE + ) + assertAvoidBadWifiSettingEquals( + "$defaultSubId,0;$testSubId,0", + getNetworkAvoidBadWifiSetting(context) + ) + assertEquals( + "0", + getNetworkLegacyGlobalAvoidBadWifiSetting(context) + ) + + setNetworkAvoidBadWifi( + context, + testSubId, + ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_PROMPT + ) + assertAvoidBadWifiSettingEquals( + "$defaultSubId,0", + getNetworkAvoidBadWifiSetting(context) + ) + assertEquals( + null, + getNetworkLegacyGlobalAvoidBadWifiSetting(context) + ) + + setNetworkAvoidBadWifi( + context, + defaultSubId, + ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_PROMPT + ) + assertAvoidBadWifiSettingEquals( + null, + getNetworkAvoidBadWifiSetting(context) + ) + assertEquals( + null, + getNetworkLegacyGlobalAvoidBadWifiSetting(context) + ) + } finally { + resetSettings( + names = arrayOf(NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI, NETWORK_AVOID_BAD_WIFI), + type = settingsTypeGlobal, + values = arrayOf(orgCarrierAwareSetting, orgSetting) + ) + } + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.BAKLAVA) + fun testGetNetworkAvoidBadWifiWithCarrierAwareSettings() { + val original = getNetworkAvoidBadWifiSetting(context) + try { + setNetworkAvoidBadWifiSetting(context, "$defaultSubId,1") + assertTrue(getNetworkAvoidBadWifi(context, defaultSubId)) + + setNetworkAvoidBadWifiSetting(context, "$defaultSubId,0") + assertFalse(getNetworkAvoidBadWifi(context, defaultSubId)) + + val testSubId = 1000 + setNetworkAvoidBadWifiSetting(context, "$defaultSubId,0;$testSubId,1") + assertFalse(getNetworkAvoidBadWifi(context, defaultSubId)) + assertTrue(getNetworkAvoidBadWifi(context, testSubId)) + + setNetworkAvoidBadWifiSetting(context, "$defaultSubId,0;$testSubId,0") + assertFalse(getNetworkAvoidBadWifi(context, defaultSubId)) + assertFalse(getNetworkAvoidBadWifi(context, testSubId)) + + setNetworkAvoidBadWifiSetting(context, "$defaultSubId,1;$testSubId,0") + assertTrue(getNetworkAvoidBadWifi(context, defaultSubId)) + assertFalse(getNetworkAvoidBadWifi(context, testSubId)) + + setNetworkAvoidBadWifiSetting(context, "$defaultSubId,1;$testSubId,1") + assertTrue(getNetworkAvoidBadWifi(context, defaultSubId)) + assertTrue(getNetworkAvoidBadWifi(context, testSubId)) + } finally { + resetSettings( + names = arrayOf(NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI), + type = settingsTypeGlobal, + values = arrayOf(original) + ) + } + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.BAKLAVA) + fun testGetNetworkAvoidBadWifiWithSettings() { + val orgCarrierAwareSetting = getNetworkAvoidBadWifiSetting(context) + val orgSetting = getNetworkLegacyGlobalAvoidBadWifiSetting(context) + try { + // test if NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI is not set, + // then it check NETWORK_AVOID_BAD_WIFI for backward compatibility. + setNetworkAvoidBadWifiSetting(context, null) + + val testSubId = 1000 + setNetworkLegacyGlobalAvoidBadWifiSetting(context, "0") + assertFalse(getNetworkAvoidBadWifi(context, defaultSubId)) + assertFalse(getNetworkAvoidBadWifi(context, testSubId)) + + setNetworkLegacyGlobalAvoidBadWifiSetting(context, "1") + assertTrue(getNetworkAvoidBadWifi(context, defaultSubId)) + assertTrue(getNetworkAvoidBadWifi(context, testSubId)) + } finally { + resetSettings( + names = arrayOf(NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI, NETWORK_AVOID_BAD_WIFI), + type = settingsTypeGlobal, + values = arrayOf(orgCarrierAwareSetting, orgSetting) + ) + } + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.BAKLAVA) + fun testGetNetworkAvoidBadWifiWithCarrierConfig() { + val orgCarrierAwareSetting = getNetworkAvoidBadWifiSetting(context) + val orgSetting = getNetworkLegacyGlobalAvoidBadWifiSetting(context) + try { + // unset the global settings, then it depends on the carrier config + mCarrierConfigRule.acquireCarrierPrivilege(defaultSubId) + setNetworkAvoidBadWifiSetting(context, null) + setNetworkLegacyGlobalAvoidBadWifiSetting(context, null) + + // default should be true if no carrier config key is set + assertTrue(getNetworkAvoidBadWifi(context, defaultSubId)) + + overrideAvoidBadWifiCarrierConfig(avoidBadWifi = true) + assertTrue(getNetworkAvoidBadWifi(context, defaultSubId)) + + overrideAvoidBadWifiCarrierConfig(avoidBadWifi = false) + assertFalse(getNetworkAvoidBadWifi(context, defaultSubId)) + } finally { + resetSettings( + names = arrayOf(NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI, NETWORK_AVOID_BAD_WIFI), + type = settingsTypeGlobal, + values = arrayOf(orgCarrierAwareSetting, orgSetting) + ) + } + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.BAKLAVA) + fun testShouldShowAvoidBadWifiToggleWithSettings() { + val orgCarrierAwareSetting = getNetworkAvoidBadWifiSetting(context) + val orgSetting = getNetworkLegacyGlobalAvoidBadWifiSetting(context) + try { + // unset the NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI, and NETWORK_AVOID_BAD_WIFI, + // it should be false since no carrier config is set + setNetworkAvoidBadWifiSetting(context, null) + setNetworkLegacyGlobalAvoidBadWifiSetting(context, null) + assertFalse(shouldShowAvoidBadWifiToggle(context, defaultSubId)) + + // if any global settings is set, it should display the toggle. + setNetworkAvoidBadWifiSetting(context, "$defaultSubId,1") + assertTrue(shouldShowAvoidBadWifiToggle(context, defaultSubId)) + + setNetworkAvoidBadWifiSetting(context, "$defaultSubId,0") + assertTrue(shouldShowAvoidBadWifiToggle(context, defaultSubId)) + + // unset the NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI, + // then it check NETWORK_AVOID_BAD_WIFI for backward compatibility. + setNetworkAvoidBadWifiSetting(context, null) + setNetworkLegacyGlobalAvoidBadWifiSetting(context, "1") + assertTrue(shouldShowAvoidBadWifiToggle(context, defaultSubId)) + + setNetworkLegacyGlobalAvoidBadWifiSetting(context, "0") + assertTrue(shouldShowAvoidBadWifiToggle(context, defaultSubId)) + } finally { + resetSettings( + names = arrayOf(NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI, NETWORK_AVOID_BAD_WIFI), + type = settingsTypeGlobal, + values = arrayOf(orgCarrierAwareSetting, orgSetting) + ) + } + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.BAKLAVA) + fun testShouldShowAvoidBadWifiToggleWithCarrierConfig() { + val orgCarrierAwareSetting = getNetworkAvoidBadWifiSetting(context) + val orgSetting = getNetworkLegacyGlobalAvoidBadWifiSetting(context) + try { + // unset both NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI and KEY_AVOID_BAD_WIFI_BOOL settings, + // then it depends on the carrier config + mCarrierConfigRule.acquireCarrierPrivilege(defaultSubId) + setNetworkAvoidBadWifiSetting(context, null) + setNetworkLegacyGlobalAvoidBadWifiSetting(context, null) + + // default should be false if KEY_AVOID_BAD_WIFI_BOOL is not set + assertFalse(shouldShowAvoidBadWifiToggle(context, defaultSubId)) + + // if KEY_AVOID_BAD_WIFI_BOOL is true, then check KEY_SHOW_AVOID_BAD_WIFI_TOGGLE_BOOL + // default should be false if KEY_SHOW_AVOID_BAD_WIFI_TOGGLE_BOOL is not set + overrideAvoidBadWifiCarrierConfig(avoidBadWifi = true) + assertFalse(shouldShowAvoidBadWifiToggle(context, defaultSubId)) + + // it should be true if KEY_AVOID_BAD_WIFI_BOOL is false + overrideAvoidBadWifiCarrierConfig(avoidBadWifi = false) + assertTrue(shouldShowAvoidBadWifiToggle(context, defaultSubId)) + + // Even if the carrier says not to show the toggle, when the carrier says + // not to avoid bad wifi it should be shown. + // This is because it should not be possible to force a device to be stuck on bad wifi + // and not give the user an option to change this. + overrideAvoidBadWifiCarrierConfig(avoidBadWifi = false, showAvoidBadWifiToggle = false) + assertTrue(shouldShowAvoidBadWifiToggle(context, defaultSubId)) + + overrideAvoidBadWifiCarrierConfig(avoidBadWifi = false, showAvoidBadWifiToggle = true) + assertTrue(shouldShowAvoidBadWifiToggle(context, defaultSubId)) + + // if KEY_AVOID_BAD_WIFI_BOOL is true, then check KEY_SHOW_AVOID_BAD_WIFI_TOGGLE_BOOL + overrideAvoidBadWifiCarrierConfig(avoidBadWifi = true, showAvoidBadWifiToggle = false) + assertFalse(shouldShowAvoidBadWifiToggle(context, defaultSubId)) + + overrideAvoidBadWifiCarrierConfig(avoidBadWifi = true, showAvoidBadWifiToggle = true) + assertTrue(shouldShowAvoidBadWifiToggle(context, defaultSubId)) + } finally { + resetSettings( + names = arrayOf(NETWORK_CARRIER_AWARE_AVOID_BAD_WIFI, NETWORK_AVOID_BAD_WIFI), + type = settingsTypeGlobal, + values = arrayOf(orgCarrierAwareSetting, orgSetting) + ) + } + } +}