Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force Android to use 3G when on local area wifi without net access

Tags:

android

wifi

3g

I have a wifi LAN setup which does not have internet access. Just various other local wifi devices connected to it. The DHCP is configured to not return a gateway or dns server. Only an IP and netmask.

When I connect my android to this wifi AP it connects fine, but all internet connectivity on the phone stops working.

I would expect that since the wifi has no gateway setting that android should realize the internet can't go through that connection and should instead be routed through the 3G connection which is at 5 bars.

I've tried setting a static IP on the android phone as well, but this did not help.

The main reason for this setup is so that the android device can transfer data on this remote network to an internet based server since it can connect to the local devices without issue. However the 3G side is broken once the wifi is setup.

Any thoughts on how to work around this issue?

like image 622
Stephen Hankinson Avatar asked Jan 13 '12 01:01

Stephen Hankinson


2 Answers

Google added some useful methods in Android SDK 21 for this purpose.

You can create NetworkRequest:

NetworkRequest networkRequest = new NetworkRequest.Builder()
    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    .build();

And then you can request such network using ConnectivityManager. For example, you want to be sure that all HTTP requests will be passed through the network with internet access. You can build your Retrofit API in this way:

ApiConfig apiConfig;

ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

connectivityManager.requestNetwork(networkRequest, new ConnectivityManager.NetworkCallback() {
    @Override
    public void onAvailable(Network network) {
        apiConfig = new Retrofit.Builder()
            .baseUrl("https://api.imatrix.io/")
            .client(new OkHttpClient.Builder()
                .socketFactory(network.getSocketFactory())
                .build())
            .build()
            .create(ApiConfig.class);
    }

    @Override
    public void onLost(Network network) {
        apiConfig = null; 
    }
});

Please, mind the thread-safety when you're using such snippet of code.

In addition, I suggest check ConnectivityManager#bindProcessToNetwork and this blog.

ConnectivityManager.NetworkCallback is an empty class and it has several methods.

like image 117
Anton Holovin Avatar answered Oct 17 '22 09:10

Anton Holovin


After a bit of coding and testing I have merged Squonk and this solution. This is the class I have created:

package it.helian.exampleprj.network;

import java.net.InetAddress;
import java.net.UnknownHostException;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo.State;
import android.net.wifi.WifiManager;
import android.text.TextUtils;
import android.util.Log;

public class NetworkUtils {
    private static final String TAG_LOG = "ExamplePrj";

    Context context;
    WifiManager wifiMan = null;
    WifiManager.WifiLock wifiLock = null;

    public NetworkUtils(Context context) {
        super();
        this.context = context;
    }

    /**
     * Enable mobile connection for a specific address
     * @param context a Context (application or activity)
     * @param address the address to enable
     * @return true for success, else false
     */
    public boolean forceMobileConnectionForAddress(Context context, String address) {
        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (null == connectivityManager) {
            Log.d(TAG_LOG, "ConnectivityManager is null, cannot try to force a mobile connection");
            return false;
        }

        //check if mobile connection is available and connected
        State state = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
        Log.d(TAG_LOG, "TYPE_MOBILE_HIPRI network state: " + state);
        if (0 == state.compareTo(State.CONNECTED) || 0 == state.compareTo(State.CONNECTING)) {
            return true;
        }

        //activate mobile connection in addition to other connection already activated
        int resultInt = connectivityManager.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, "enableHIPRI");
        Log.d(TAG_LOG, "startUsingNetworkFeature for enableHIPRI result: " + resultInt);

        //-1 means errors
        // 0 means already enabled
        // 1 means enabled
        // other values can be returned, because this method is vendor specific
        if (-1 == resultInt) {
            Log.e(TAG_LOG, "Wrong result of startUsingNetworkFeature, maybe problems");
            return false;
        }
        if (0 == resultInt) {
            Log.d(TAG_LOG, "No need to perform additional network settings");
            return true;
        }

        //find the host name to route
        String hostName = extractAddressFromUrl(address);
        Log.d(TAG_LOG, "Source address: " + address);
        Log.d(TAG_LOG, "Destination host address to route: " + hostName);
        if (TextUtils.isEmpty(hostName)) hostName = address;

        //create a route for the specified address
        int hostAddress = lookupHost(hostName);
        if (-1 == hostAddress) {
            Log.e(TAG_LOG, "Wrong host address transformation, result was -1");
            return false;
        }
        //wait some time needed to connection manager for waking up
        try {
            for (int counter=0; counter<30; counter++) {
                State checkState = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
                if (0 == checkState.compareTo(State.CONNECTED))
                    break;
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            //nothing to do
        }
        boolean resultBool = connectivityManager.requestRouteToHost(ConnectivityManager.TYPE_MOBILE_HIPRI, hostAddress);
        Log.d(TAG_LOG, "requestRouteToHost result: " + resultBool);
        if (!resultBool)
            Log.e(TAG_LOG, "Wrong requestRouteToHost result: expected true, but was false");

        state = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
        Log.d(TAG_LOG, "TYPE_MOBILE_HIPRI network state after routing: " + state);

        return resultBool;
    }

    /**
     * This method extracts from address the hostname
     * @param url eg. http://some.where.com:8080/sync
     * @return some.where.com
     */
    public String extractAddressFromUrl(String url) {
        String urlToProcess = null;

        //find protocol
        int protocolEndIndex = url.indexOf("://");
        if(protocolEndIndex>0) {
            urlToProcess = url.substring(protocolEndIndex + 3);
        } else {
            urlToProcess = url;
        }

        // If we have port number in the address we strip everything
        // after the port number
        int pos = urlToProcess.indexOf(':');
        if (pos >= 0) {
            urlToProcess = urlToProcess.substring(0, pos);
        }

        // If we have resource location in the address then we strip
        // everything after the '/'
        pos = urlToProcess.indexOf('/');
        if (pos >= 0) {
            urlToProcess = urlToProcess.substring(0, pos);
        }

        // If we have ? in the address then we strip
        // everything after the '?'
        pos = urlToProcess.indexOf('?');
        if (pos >= 0) {
            urlToProcess = urlToProcess.substring(0, pos);
        }
        return urlToProcess;
    }

    /**
     * Transform host name in int value used by {@link ConnectivityManager.requestRouteToHost}
     * method
     *
     * @param hostname
     * @return -1 if the host doesn't exists, elsewhere its translation
     * to an integer
     */
    private int lookupHost(String hostname) {
        InetAddress inetAddress;
        try {
            inetAddress = InetAddress.getByName(hostname);
        } catch (UnknownHostException e) {
            return -1;
        }
        byte[] addrBytes;
        int addr;
        addrBytes = inetAddress.getAddress();
        addr = ((addrBytes[3] & 0xff) << 24)
                | ((addrBytes[2] & 0xff) << 16)
                | ((addrBytes[1] & 0xff) << 8 )
                |  (addrBytes[0] & 0xff);
        return addr;
    }

    @SuppressWarnings("unused")
    private int lookupHost2(String hostname) {
        InetAddress inetAddress;
        try {
            inetAddress = InetAddress.getByName(hostname);
        } catch (UnknownHostException e) {
            return -1;
        }
        byte[] addrBytes;
        int addr;
        addrBytes = inetAddress.getAddress();
        addr = ((addrBytes[3] & 0xff) << 24)


        | ((addrBytes[2] & 0xff) << 16)
            | ((addrBytes[1] & 0xff) << 8 )
            |  (addrBytes[0] & 0xff);
        return addr;
    }

    public Boolean disableWifi() {
        wifiMan = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        if (wifiMan != null) {
            wifiLock = wifiMan.createWifiLock(WifiManager.WIFI_MODE_SCAN_ONLY, "HelianRCAWifiLock");
        }
        return wifiMan.setWifiEnabled(false);
    }

    public Boolean enableWifi() {
        Boolean success = false;

        if (wifiLock != null && wifiLock.isHeld())
            wifiLock.release();
        if (wifiMan != null)
        success = wifiMan.setWifiEnabled(true);
        return success;
    }
}

This is the usage:

USAGE CODE

            boolean mobileRoutingEnabled = checkMobileInternetRouting();

            if(!mobileRoutingEnabled) {
                networkUtils.disableWifi();

                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

            networkUtils.forceMobileConnectionForAddress(context, RCA_URL);

            if(!mobileRoutingEnabled) {
                networkUtils.enableWifi();
            }

            // This second check is for testing purpose
            checkMobileInternetRouting();

            return callWebService(RCA_COMPLETE_URL, _plate);

where checkMobileInternetRouting is:

private boolean checkMobileInternetRouting() {
    ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

    State state = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
    return 0 == state.compareTo(State.CONNECTED) || 0 == state.compareTo(State.CONNECTING);
}

USAGE PROCEDURE

  1. Check if the routing to the host is enabled
  2. If yes go with the communication regardless the wifi is connected or not and execute only points 6 (the point 4 will only check that routing is already enable without executing any rilevant action). Otherwise temporary disables the wifi.
  3. Thread sleep of about 3 seconds for letting the 3g connection comes back
  4. Set the 3g routing to the given url
  5. Enable back the wifi
  6. Now the given url can be called even with a wifi connection without net acces

CONCLUSIONS

This is a bit hacky but works properly. The only problem is that this routing has got a timeout of few seconds (like 20-30) that forces you to execute the entire above procedure once more. Setting this timeout to a higher value would be very good.

like image 20
Link 88 Avatar answered Oct 17 '22 08:10

Link 88