Forzar que una petición HTTP se envíe por la WiFi seleccionada

En uno de los proyectos en los que trabajo, nos conectamos a un pequeño dispositivo para leer / escribir datos en el.

La primera vez que se arrranca el dispositivo, este lo hace en un modo “Access Point” que crea una WiFi virtual, desde la que nos podemos conectar al dispositivo directamente para configurarle la red WiFi a la que se tiene que conectar. Este punto de acceso, evidentemente no da salida a Internet.

En Android Marshmallow, cuando el dispositivo está en punto de acceso y, al no ofrecer una conexión que el sistema Android considere de “calidad”, este decide enviar las peticiones siempre por la red 4g/3g. Haciendo imposible la configuración del dispositivo desde esta versión del sistema.

El siguiente código inicializa OkHttpClient para que siempre envie la petición por la primera wifi que tiene configurada (la que tienes seleccionada) en lugar de la que ofrezca más calidad.

Pongo aquí el código por si a alguien le sirviese de utilidad. Básicamente consiste en sobreescribir la clase OkHttpClient añadiéndole un interceptor.

public class DeviceHttpClient extends OkHttpClient {

    private static final String TAG = "DeviceHttpClient";

    public DeviceHttpClient(final Context context) {

        // Configure Timeouts
        setConnectTimeout(Constants.CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);

        try {
            // Configure Retry policy and Prioritize Wifi
            interceptors().add(
            new Interceptor() {
                   @Override
                   public Response intercept(Chain chain) throws IOException {
                       try {
                           boundRequestToWifi(context);
                           Request request = chain.request();
                           return chain.proceed(request);
                       } finally {
                           unBoundRequestToWifi(context);
                       }
                   }
               }
            );
        } catch (Exception e) {
            Log.e(TAG, "Error while initializing http client", e);
        }
    }

    @SuppressWarnings("deprecation")
    private void boundRequestToWifi(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

            Network[] networks = connectivityManager.getAllNetworks();
            if (networks != null && networks.length > 0) {
                for (Network network : networks) {
                    NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);

                    if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                            connectivityManager.bindProcessToNetwork(network);
                        }
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
                            Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                            ConnectivityManager.setProcessDefaultNetwork(network);
                        }
                        break;
                    }
                }
            }
        }
    }

    @SuppressWarnings("deprecation")
    private void unBoundRequestToWifi(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                connectivityManager.bindProcessToNetwork(null);
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
                Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                ConnectivityManager.setProcessDefaultNetwork(null);
            }
        }
    }
}

Actualización OkHttpClient 3 Se debe utilizar este interceptor:

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.os.Build;

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.Response;

public class BoundWifiInterceptor implements Interceptor {

    private final ConnectivityManager connectivityManager;

    public BoundWifiInterceptor(Context context) {
        this.connectivityManager = (ConnectivityManager) context.getSystemService(
                Context.CONNECTIVITY_SERVICE);
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        try {
            bindProcessToFirstWifiNetwork();
            return chain.proceed(chain.request());
        } finally {
            unbindProcessFromFirstWifiNetwork();
        }
    }

    @SuppressWarnings("deprecation")
    private void bindProcessToFirstWifiNetwork() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Network[] networks = connectivityManager.getAllNetworks();
            if (networks != null && networks.length > 0) {
                for (Network network : networks) {
                    NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
                    if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                            connectivityManager.bindProcessToNetwork(network);
                        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                            ConnectivityManager.setProcessDefaultNetwork(network);
                        }
                        break;
                    }
                }
            }
        }
    }

    @SuppressWarnings("deprecation")
    private void unbindProcessFromFirstWifiNetwork() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                connectivityManager.bindProcessToNetwork(null);
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                ConnectivityManager.setProcessDefaultNetwork(null);
            }
        }
    }
}