androidkotlinandroid-networkingandroid-connectivitymanager

Is it relevant to check network capabilities if I specified them in the network request


In my code, I need to track the internet connection and make appropriate changes. First of all, I create a network request, then register a network callback using the ConnectivityManager.

    val networkCallback = createNetworkCallback()
    val networkRequest = NetworkRequest.Builder()
        .addCapability(NET_CAPABILITY_INTERNET)
        .addTransportType(TRANSPORT_CELLULAR)
        .addTransportType(TRANSPORT_WIFI)
        .addTransportType(TRANSPORT_ETHERNET)
        .build()
    connectivityManager.registerNetworkCallback(networkRequest, networkCallback)

Let's look at networkRequest. The network I'm requesting need to have a NET_CAPABILITY_INTERNET and some transport types which I've specified in the code. Then I register a network callback. As per docs - Registers to receive notifications about all networks which satisfy the given NetworkRequest.

Means, e.g. I'll turn WI-FI on/off, the network callback will fire only if I've specified that transport type or corresponding capabilities inside the network request. Now the question is, when the networkCallback triggers, do I need to check for the capabilities and transport types again?

override fun onAvailable(network: Network) {
        val networkCapabilities = cm.getNetworkCapabilities(network)
        val hasInternetCapabilities = networkCapabilities?.let {
            return@let it.hasCapability(NET_CAPABILITY_INTERNET) or
                it.hasTransport(TRANSPORT_WIFI) or
                it.hasTransport(TRANSPORT_CELLULAR) or
                it.hasTransport(TRANSPORT_ETHERNET)
        } ?: false
    }
}

As I've understood, if the network will not satisfy my networkRequest, the check inside onAvailable() is irrelevant because it won't execute. Thank you.


Solution

  • You are correct, the additional check you have in onAvailable is unnecessary. The platform will only send onAvailable for networks that satisfy the passed in NetworkRequest (otherwise, the API would be pretty pointless).

    Here is some official docs (link) on the topic:

    The app builds a NetworkRequest to inform ConnectivityManager of what kind of networks it wants to listen to. For example, if your app is only interested in unmetered internet connections:

    NetworkRequest request = new NetworkRequest.Builder()
      .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
      .addCapability(NET_CAPABILITY_INTERNET)
      .build();
    connectivityManager.registerNetworkCallback(request, myNetworkCallback);
    

    This would ensure that your app hears about all changes concerning any unmetered network on the system.

    Note the below is accurate at the time of writing this answer.

    For callbacks, in the Connectivity stack in Android they are known as listen requests, therefore if you wanted to see how they interplay with sending onAvailable it would be here in processNewlySatisfiedListenRequests:

    private void processNewlySatisfiedListenRequests(@NonNull final NetworkAgentInfo nai) {
            for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
                if (nri.isMultilayerRequest()) {
                    continue;
                }
                final NetworkRequest nr = nri.mRequests.get(0);
                if (!nr.isListen()) continue;
                if (nai.satisfies(nr) && !nai.isSatisfyingRequest(nr.requestId)) {
                    nai.addRequest(nr);
                    notifyNetworkAvailable(nai, nri);
                }
            }
        }
    

    ConnectivityService which controls all of this is pretty hard to follow, but if you follow the code path that lands here, you'll see it comes from the method computeNetworkReassignment here.

    The important line of code in the above is this:

    bestNetwork = mNetworkRanker.getBestNetwork(req, nais, nri.getSatisfier());
    

    Drilling into that method you'll see that it calls NetworkRanker which runs satisfies against the originally passed in NetworkRequest:

    final ArrayList<NetworkAgentInfo> candidates = filter(nais, nai -> nai.satisfies(request));
    

    Given the above, you can see that if you receive an onAvailable for a NetworkRequest, it will satisfy whatever the original NetworkCapabilities were passed in therefore it isn't necessary to check them again to confirm.