Отключение Samsung "Автоматический сетевой коммутатор" для подключения WiFi

В нашем приложении мы обрабатываем подключение Wi-Fi к устройству, которое передает свою собственную точку беспроводного доступа (без подключения к Интернету) для прямой связи.

Он отлично работает на всех наших тестовых устройствах; однако мы получаем отчеты от пользователей о том, что на некоторых устройствах Samsung (Galaxy S4, Galaxy Note 3) есть настройка в настройках Wi-Fi под названием "Auto Network Switch", добавленная Samsung, которая ищет "нестабильные" сети, и автоматически отключится и вернется к мобильным данным. К сожалению, поскольку у нашего устройства нет подключения к Интернету, Samsung сообщает об этом как о нестабильной сети и немедленно отключается.

У меня нет ни одного из этих устройств, доступных для тестирования, поэтому мне любопытно, знает ли кто-нибудь об этой проблеме или знает, как либо программно отключить этот параметр, либо обойти его?

Код, который мы используем для соединения:

/**
 * Attempt to connect to an open wifi network with the given SSID
 * @param ssid the SSID of the unsecured wireless network to connect to
 */
public static void connectToOpenNetwork (String ssid) {
    WifiManager mgr = getManager();
    WifiConfiguration configuration = getOpenWifiConfiguration(ssid);
    mgr.addNetwork(configuration);
    mgr.saveConfiguration();

    int res = findOpenNetworkId(ssid);
    if (res != INVALID_NETWORK_ID) {
        mgr.enableNetwork(res, true);
        mgr.reconnect();
    } else {
        Log.e(TAG, "Received request to connect to network " + ssid + " but the network was not found in the configurations.");
    }
}

/**
 * Get a WifiConfiguration object configured for an unsecured wireless network with the
 * given SSID.
 * @param ssid the SSID of the network to configure
 * @return a WifiConfiguration object that can be passed to
 * {@link WifiManager#addNetwork(android.net.wifi.WifiConfiguration)}
 */
private static WifiConfiguration getOpenWifiConfiguration (String ssid) {
    WifiConfiguration config = new WifiConfiguration();

    config.SSID = "\"" + ssid + "\"";
    config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);

    return config;
}

Ответ 1

EDIT: Итак, дальнейшие исследования для заинтересованных, похоже, это функция, добавленная в версиях Samsung Touchwiz на основе 4.3. Внутри параметр имеет значение "wifi_watchdog_connectivity_check". Я все еще использую приведенный ниже код, чтобы узнать, могу ли я проверить, включен ли этот параметр, но в противном случае я должен был бы принять его.

Итак, что я обнаружил, так это то, что после попытки соединения, и ОС отключается от сети, конфигурация Wi-Fi находится в состоянии "отключено". Поэтому после возникновения проблемы вы можете точно убедиться, что это произошло, проверив состояние конфигурации с WifiManager.

WifiManager m = (WifiManger) getSystemService(Context.WIFI_SERVICE);
List<WifiConfiguration> networks = m.getConfiguredNetworks();
String mySsid = "My Network";
mySsid = "\"" + mySsid + "\"";

boolean isDisabled = false;
for (WifiConfiguration config : networks) {
    if (mySsid.equals(config.SSID)) {
        if (config.status = WifiConfiguration.Status.DISABLED) {
            isDisabled = true;
            break;
        }
    }
}

//If isDisabled is true, the network was disabled by the OS

Затем вы можете попытаться разрешить имя параметра из приложения системных настроек:

/** Gets the resources of another installed application */
private static Resources getExternalResources(Context ctx, String namespace) {
    PackageManager pm = ctx.getPackageManager();
    try {
        return (pm == null) ? null : pm.getResourcesForApplication(namespace);
    } catch (PackageManager.NameNotFoundException ex) {
        return null;
    }
}

/** Gets a resource ID from another installed application */
private static int getExternalIdentifier(Context ctx, String namespace, 
        String key, String type) {
    Resources res = getExternalResources(ctx, namespace);
    return (res == null) ? 0 : res.getIdentifier(key, type, namespace);
}

/** Gets a String resource from another installed application */
public static String getExternalString(Context ctx, String namespace, 
        String key, String defVal) {
    int resId = getExternalIdentifier(ctx, namespace, key, "string");
    if (resId != 0) {
        Resources res = getExternalResources(ctx, namespace);
        return res.getString(resId);
    } else {
        return defVal;
    }
}

Затем используйте его, чтобы получить строку:

String autoNetworkSwitch = getExternalString(this, "com.android.settings",
        "wifi_watchdog_connectivity_check", "Unknown");

Это вернет локализованную строку для текущего языка пользователя, если строка существует.


Для всех, кто интересуется результатами этого, выясняется, что этот вариант на самом деле является базовым набором Android, но, похоже, он более агрессивен на этих устройствах Samsung. Параметр - скрытая настройка, найденная в android.provider.Settings.java:

/**
 * Setting to turn off poor network avoidance on Wi-Fi. Feature is enabled by default and
 * the setting needs to be set to 0 to disable it.
 * @hide
 */
public static final String WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED =
       "wifi_watchdog_poor_network_test_enabled";

Что находится в Settings$Secure для API == 15 || API == 16, или Settings$Global для API >= 17. Это не параметр, который может быть включен или отключен сторонними приложениями; однако его можно обнаружить и предупредить. Мое решение таково:

import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;

/**
 * Checks whether the "Avoid poor networks" setting (named "Auto network switch" on 
 * some Samsung devices) is enabled, which can in some instances interfere with Wi-Fi.
 *
 * @return true if the "Avoid poor networks" or "Auto network switch" setting is enabled
 */
public static boolean isPoorNetworkAvoidanceEnabled (Context ctx) {
    final int SETTING_UNKNOWN = -1;
    final int SETTING_ENABLED = 1;
    final String AVOID_POOR = "wifi_watchdog_poor_network_test_enabled";
    final String WATCHDOG_CLASS = "android.net.wifi.WifiWatchdogStateMachine";
    final String DEFAULT_ENABLED = "DEFAULT_POOR_NETWORK_AVOIDANCE_ENABLED";
    final ContentResolver cr = ctx.getContentResolver();

    int result;

    if (SDK_INT >= JELLY_BEAN_MR1) {
        //Setting was moved from Secure to Global as of JB MR1
        result = Settings.Global.getInt(cr, AVOID_POOR, SETTING_UNKNOWN);
    } else if (SDK_INT >= ICE_CREAM_SANDWICH_MR1) {
        result = Settings.Secure.getInt(cr, AVOID_POOR, SETTING_UNKNOWN);
    } else {
        //Poor network avoidance not introduced until ICS MR1
        //See android.provider.Settings.java
        return false;
    }

    //Exit here if the setting value is known
    if (result != SETTING_UNKNOWN) {
        return (result == SETTING_ENABLED);
    }

    //Setting does not exist in database, so it has never been changed.
    //It will be initialized to the default value.
    if (SDK_INT >= JELLY_BEAN_MR1) {
        //As of JB MR1, a constant was added to WifiWatchdogStateMachine to determine 
        //the default behavior of the Avoid Poor Networks setting.
        try {
            //In the case of any failures here, take the safe route and assume the 
            //setting is disabled to avoid disrupting the user with false information
            Class wifiWatchdog = Class.forName(WATCHDOG_CLASS);
            Field defValue = wifiWatchdog.getField(DEFAULT_ENABLED);
            if (!defValue.isAccessible()) defValue.setAccessible(true);
            return defValue.getBoolean(null);
        } catch (IllegalAccessException ex) {
            return false;
        } catch (NoSuchFieldException ex) {
            return false;
        } catch (ClassNotFoundException ex) {
            return false;
        } catch (IllegalArgumentException ex) {
            return false;
        }
    } else {
        //Prior to JB MR1, the default for the Avoid Poor Networks setting was
        //to enable it unless explicitly disabled
        return true;
    }
}

В целях хорошей оценки вы можете направлять своих пользователей в Advanced Wi-Fi Settings с помощью Intent:

/**
 *  Ensure that an Activity is available to receive the given Intent
 */
public static boolean activityExists (Context ctx, Intent intent) {
    final PackageManager mgr = ctx.getPackageManager();
    final ResolveInfo info = mgr.resolveActivity(i, PackageManager.MATCH_DEFAULT_ONLY);
    return (info != null);
}

public static void showAdvancedWifiIfAvailable (Context ctx) {
    final Intent i = new Intent(Settings.ACTION_WIFI_IP_SETTINGS);
    if (activityExists(ctx, i)) {
        ctx.startActivity(i);
    }
}

Интересные мелочи: этот Intent вызывает тот же Activity, что и в настройках > Wi-Fi > Дополнительно, но покажет его с другим заголовком (настройки IP, vs Advanced Wi-Fi).