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) + ) + } + } +}