PatrickDelaigle
PatrickDelaigle

Reputation: 77

Force a specific network in android even if it doesn't have internet connectivity

My project is to make a rear view camera using an old USB webcam and a Raspberry Pi.

I plugged the cam to the pi and using "motion", and I can access the live video with a browser (http://<ip-address>:8081), when I am on the same network as the Pi.

For that, I turned my Pi to a wifi hotspot, in order to be in the same network with my phone to access the video.

But: After I created an application with Android Studio, with a WebView to open the video link, I get a ERR_CONNECTION_REFUSED when mobile data is enabled. Since the Wifi-Network doesn't get used if no internet connection is detected.

I don't want to manually disable it each time I want to use it, I want the application to use the Wi-Fi ONLY.

I managed to auto connect to the pi WiFi using this code :

    WifiNetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
            .setSsid("Wifi-Fourcon")
            .setWpa2Passphrase("1234567890")
            .build();

    final NetworkRequest request = new NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) // we want WiFi
            .setNetworkSpecifier(specifier) // we want _our_ network
            .build();

    ConnectivityManager connectivityManager = (ConnectivityManager)this.getSystemService(CONNECTIVITY_SERVICE);

    connectivityManager.requestNetwork(request,

I tried to add

.removeTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)

But same result.

I also tried to disable mobile data with

setMobileDataEnabled()

But we cannot use this method anymore..

Upvotes: 2

Views: 2749

Answers (2)

JensV
JensV

Reputation: 4544

So I'm assuming the problem here is that the Wifi connection doesn't actually provide internet, so android ignores the connection for web requests.


Edit: After reading through the docs, the method bindProcessToNetwork seems to fit the use-case much better. It will limit the whole process to only use that specific network. I have once again not tested this, but it should be easily implemented by calling the method right in onAvailable and call it with null in onLost if you want to reverse the binding if needed. Existing sockets won't be forced, so the safest way would be to initialize the WebView only after binding to the network.


One can circumvent this by overriding how the WebView loads it's request and force it to use the connection of your Network request. A complete solution would probably look like this, altough I have not tested it. Let me know if this works:

Network networkInstance;

void getNetwork(Context context) {

    final NetworkRequest request = new NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) // we want WiFi
            .setNetworkSpecifier("foobar") // we want _our_ network
            .build();


    ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    connectivityManager.requestNetwork(request, new ConnectivityManager.NetworkCallback() {
        @Override
        public void onAvailable(@NonNull Network network) {
            networkInstance = network;
            loadWebView();
        }

        @Override
        public void onLost(@NonNull Network network) {
            // Notify the user or something ...
            super.onLost(network);
        }
    });

}


public void setupWebView(WebView webView) {


    webView.setWebViewClient(new WebViewClient() {
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {

            if (networkInstance != null) {
                try {
                    URL url = new URL(request.getUrl().toString());
                    HttpURLConnection connection = (HttpURLConnection) networkInstance.openConnection(url);
                    connection.setDoOutput(true);
                    connection.setRequestMethod(request.getMethod());

                    for (Map.Entry<String, String> header : request.getRequestHeaders().entrySet()) {
                        connection.setRequestProperty(header.getKey(), header.getValue());
                    }

                    int responseCode = connection.getResponseCode();
                    String responseMessage = connection.getResponseMessage();
                    String contentType = connection.getContentType();
                    String encoding = connection.getContentEncoding();

                    // Transform response headers from Map<String, List<String>> to Map<String, String>
                    HashMap<String, String> headerFields = new HashMap<>();
                    for (Map.Entry<String, List<String>> entry : connection.getHeaderFields().entrySet()) {
                        headerFields.put(entry.getKey(), entry.getValue().get(0));
                    }

                    String[] contentTypeData = contentType.split(";\\s?");
                    contentType = contentTypeData[0];
                    if (contentTypeData.length > 1) {
                        encoding = contentTypeData[1];
                    }

                    if (contentType.contains("text") && encoding == null) {
                        encoding = "UTF-8";
                    }

                    InputStream inputStream;
                    try {
                        inputStream = connection.getInputStream();
                    } catch (Exception e) {
                        inputStream = connection.getErrorStream();
                    }

                    return new WebResourceResponse(
                            contentType,
                            encoding,
                            responseCode,
                            responseMessage,
                            headerFields,
                            inputStream
                    );

                } catch (IOException e) {
                    e.printStackTrace();
                }

            }

            return super.shouldInterceptRequest(view, request);
        }
    });
}

setupWebView should be called once with the WebView instance to set the WebViewClient. (If you are already using a WebViewClient, just copy the overridden shouldInterceptRequest method.)

Upvotes: 2

Tymur Pysarevych
Tymur Pysarevych

Reputation: 69

Google disabled the ability to modify critical Android features such as enable/disable mobile internet.

Upvotes: 0

Related Questions