1

The moment I get on a wifi connection, the cellular network is completely lost even though the cellular network indicator is definitely on.

This is my network request

val request = NetworkRequest.Builder().run {
    addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
    build()
  }

connectivityManager.registerNetworkCallback(request, callback)

I've tried looking in the connectivityManager.allNetworks list and it's no where to be found. Only the wifi network is in there.

What's even weirder is there is one other cellular network that is always there. It does not have the same ID as my cellular network. There's no connection that can be made with it. It never shows up with registerNetworkCallback. The capabilities on it always include "valid" and "internet"

What am I seeing here? Why is my cellular network lost? What is this phantom cellular network?

  • targetSdkVersion: 29
  • Device: Galaxy S10 - Android 12
thomasttvo
  • 33
  • 4

3 Answers3

2

I figured this out.

If you call registerNetworkCallback the above will happen, but if you call requestNetwork with TRANSPORT_CELLULAR,

connectivityManager.requestNetwork(request, callback)

Android will keep the cellular network around. I was so confused because the documentation was so lacking. Once you do that, it will ask you to add the CHANGE_NETWORK_STATE permission.

After this step, the network is available, but you won't be able to make any request with it. You have to call

connectivityManager.bindProcessToNetwork(theCellularNetwork)

to get any connection.

After this is done, the cellular network can be used in tandem with the wifi network. You can even send some traffic to one and some to the other. If you use OkHttp like I do, you just bind the client with the network's socketFactory

val client = OkHttpClient().newBuilder().run {
  socketFactory(network.socketFactory)
  build()
}

client.newCall(
  Request.Builder().url("https://example.com").build()
).execute().let {
  Log.i(TAG, "Fetched ${it.body!!.string()}")
}    
thomasttvo
  • 33
  • 4
1

The cellular network isn't lost, but your app isn't allowed to use it. Once WiFi is connected, everything is forced to use that connection. The only exception to this rule is if your phone has a feature called "Dual Acceleration", which allows the cellular connection to stay active (and obviously, the user would have to enable that feature). Alternatively, you may have a setting in your phone's Developer Options called "Cellular Data Always Active", which will do the same thing.

But needless to say, you can't rely on either of those 2 features being enabled in a production environment. So, just assume that when WiFi is connected, that's the only connection that your app can use

user496854
  • 6,461
  • 10
  • 47
  • 84
  • This is only correct if you are using the "default" network. The answer @thomasttvo posted is how you go about connecting to two networks at once. – Always Learning Aug 19 '22 at 15:35
0

We can maintain a cellular connection (without disruption) in the presence or absence of Wi-Fi connections by using ConnectivityManager's requestNetwork() with a transport type of TRANSPORT_CELLULAR -and- Internet reach capability (NET_CAPABILITY_INTERNET).

I've found that without requiring the Internet capability, availability of a Wi-Fi connection does affect an existing cellular network object (and connections bound to that network as a result): The old cellular network is lost, and a new cellular network (with new Network ID) is made available, often with different downlink and uplink speeds (this is all while requiring TRANSPORT_CELLULAR transport; downlink/uplink speed change may be carrier dependent).

For binding/constraining a connection to a given network, create an unconnected socket, bind it to the desired network, and then connect the socket. Binding a socket to a given network is more granular than (and also overrides) process level setting through bindProcessToNetwork(). See sample code below.

private final HashSet<Long> networks = new HashSet<>(); // matching networks
private Socket socket = null; // connection

// network availabilty callbacks
NetworkRequest networkRequest = new NetworkRequest.Builder().
        addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).
        addTransportType(TRANSPORT_CELLULAR). // any transport type
        build();

ConnectivityManager.NetworkCallback networkCallback =
        new ConnectivityManager.NetworkCallback() {
    @Override
    public void onAvailable(Network network) {
        networks.add(network.getNetworkHandle());
        super.onAvailable(network);
    }
    @Override
    public void onLost(Network network) {
        networks.remove(network.getNetworkHandle());
        super.onLost(network);
    }
};
connectivityManager.requestNetwork(networkRequest, networkCallback);

// your connection logic here
public void connectToSomething() {
    if (socket == null && !networks.isEmpty()) {
        long handle = networks.iterator().next();
        Network network = Network.fromNetworkHandle(handle);
        try {
            socket = new Socket();
            network.bindSocket(socket); // <-- bind to network
            socket.connect(new InetSocketAddress(host, port));
        } catch (IOException e) { socket = null; }
    }
    if (socket != null) {
        try {
            // use the socket ...
        } catch (IOException e1) {
            try { socket.close(); } catch (IOException e2) { }
            socket = null;
        }
    }
}
P.T.
  • 13
  • 4