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

5

15

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?

Deputize answered 13/1, 2012 at 1:36 Comment(1)
Did you find a solution for this? I have this exact same problem. I have a Raspberry Pi that's an Airplay server and has it own wireless network . In order to stream stuff to it I have to be connected to its network but I while I'm connected I do not have "carrier internet connectivity". With an iPhone i can assign a static IP with no DNS/Default gateway and it works perfectly. I just cant achieve that in Android.Charity
B
5

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.

Broderick answered 25/6, 2014 at 16:2 Comment(0)
W
1

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.

Wismar answered 11/2, 2018 at 20:41 Comment(0)
C
0

From code, when you detect there is no connectivity, you could switch off WiFi...

As for a setting, there is none (no good way to check if there really is connectivity universally and reliably). But some phones do just what you describe automatically, like for example my LG P-970.

(Note: Android disconnects from mobile networks when it connects to a WiFi, so there is no way to still be connected to a WiFi but route internet access through mobile, even though Linux can do it (with the ip route ... suite of tools))

Coltun answered 13/1, 2012 at 1:41 Comment(3)
Thanks. The iPhone seems to work the same as your LG-P-970. However for this specific case I'd need it to work with an Android. If the android disconnects from the mobile network, are calls then routed through the wifi connection? Or is only data disabled on the mobile network?Deputize
P-970 is Android. Only mobile data is disabled while on wifi of course. It might be worth trying to assign a link-local IP (Apipa) from the 169.254 range.Coltun
Thanks. I tried link local but that didn't help for my Android. This was very useful information though as I have use it in a product that I am in the process of building and it solved an issue. Thanks!Deputize
M
0

I can't guarantee this will work as it's something I only experimented with some time ago. I had a similar need to use 3G (or other mobile network) when the wifi-connected network had no route to the outside world.

The following code should drop the wifi connection in order to allow the mobile network to come in to play. You'll need to do various tests along the way and re-establish the wifi connection again afterwards...

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

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

private Boolean enableWifi() {
    Boolean success;

    if (wifiLock != null)
        wifiLock.release();
    if (wifiMan != null)
        success = wifiMan.setWifiEnabled(true);
    return success;
}
Monkshood answered 13/1, 2012 at 2:17 Comment(3)
Thanks. It would be ideal if I could have both networks active at the same time. I'll look into this though.Deputize
From what I've read, standard Android behaviour is to disable mobile internet connection if a wifi connection is available. That's certainly the case with my HTC Desire. I came up with the above code very early in my Android learning so there may be better ways of doing things although I haven't come across anything else along the way.Monkshood
Yeah, for the specific application that I am targeting, this is really bad behavior. It's too bad that the android doesn't behave like a PC or Mac in this case. Thanks for the code though, it will most likely come in handy at some point.Deputize
D
-2

you don't need to code anything. i found an app that do exactly this thing. you can configure to disconnect automatically from the wifi if there is no internet from this connection.

https://play.google.com/store/apps/details?id=com.nLabs.internetconnectivity&hl=en

Deweese answered 27/2, 2015 at 16:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.