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

后端 未结 5 1457
醉酒成梦
醉酒成梦 2020-12-14 10:58

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 ser

相关标签:
5条回答
  • 2020-12-14 11:00

    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.

    0 讨论(0)
  • 2020-12-14 11:09

    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.

    0 讨论(0)
  • 2020-12-14 11:15

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

    0 讨论(0)
  • 2020-12-14 11:26

    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

    0 讨论(0)
  • 2020-12-14 11:27

    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;
    }
    
    0 讨论(0)
提交回复
热议问题