How to use ConnectivityManager.NetworkCallback for wifi and mobile status?

Viewed 1522

The precondition is using wifi and mobile network at the same time. onLost function of ConnectivityManager.NetworkCallback is called suddenly after I turned off wifi. That's why NetworkInfo from ConnectivityManager is null and that is the problem here.

But If I put Thread.sleep() for 3 seconds, it works well.

I wonder what the right way is to use ConnectivityManager.NetworkCallback for checking the network status.

new ConnectivityManager.NetworkCallback() {
    @Override
    public void onAvailable(Network network) {
        Log.d(TAG, "ConnectivityManager.NetworkCallback:onAvailable");
        context.sendBroadcast(getNetworkStateIntent(isAvailable(context)));
    }

    @Override
    public void onLosing(Network network, int maxMsToLive) {
        Log.d(TAG, "ConnectivityManager.NetworkCallback:onLosing");
    }

    @Override
    public void onLost(Network network) {
        Log.d(TAG, "ConnectivityManager.NetworkCallback:onLost");
        context.sendBroadcast(getNetworkStateIntent(isAvailable(context)));
    }

    @Override
    public void onUnavailable() {
        Log.d(TAG, "ConnectivityManager.NetworkCallback:onUnavailable");
        context.sendBroadcast(getNetworkStateIntent(isAvailable(context)));
    }
};
private boolean isAvailable(Context context) {
    Log.d(TAG, "isAvailable");
    ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
    Log.d(TAG, "activeNetworkInfo:" + activeNetworkInfo);
    NetworkInfo mWifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
    Log.d(TAG, "wifi:" + mWifi);
    NetworkInfo mMobile = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
    Log.d(TAG, "mobile:" + mMobile);

    if(activeNetworkInfo == null) {
        Log.d(TAG, "false 'cause activeNetworkInfo is null");
        return false;
    }

    int type = activeNetworkInfo.getType();
    switch(type) {
        case ConnectivityManager.TYPE_WIFI:
        case ConnectivityManager.TYPE_MOBILE:
        case ConnectivityManager.TYPE_ETHERNET:
            Log.d(TAG, "true");
            return true;
        default:
            Log.d(TAG, "false 'cause of type");
            return false;
    }
}
1 Answers

Based on the assumption that you are constantly listening to determine whether or not we have an internet access on the device, i've done the following :

  1. Storing the list of networks in a LiveData class and add networks in this list on new onAvailable callback, so that we store all the remaining active networks somewhere. (there must be a way to get them in ConnectivityManager but I prefer this way)
  2. When user or system disable a network, we remove the network from networks ArrayList
  3. Every time we lose the network, we remove it from the list and we check if all remaining network in the list are able to perform a query on a root-servers.net server, in order to determine if we have an internet access or not.

Here is the code of the LiveData class

class LiveDataNetworkStatus(context: Context) : LiveData<Boolean>() {

    companion object {
        const val ROOT_SERVER_CHECK_URL = "a.root-servers.net"
    }

    private val connectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
    private val networks: ArrayList<Network> = ArrayList()

    private val networkStateObject = object : ConnectivityManager.NetworkCallback() {
        override fun onLost(network: Network) {
            super.onLost(network)
            networks.remove(network)
            postValue(!networks.all { !checkInternetConnectivity(it) })
        }

        override fun onAvailable(network: Network) {
            super.onAvailable(network)
            networks.add(network)
            postValue(checkInternetConnectivity(network))
        }

        fun checkInternetConnectivity(network: Network): Boolean {
            return try {
                network.getByName(ROOT_SERVER_CHECK_URL) != null
            } catch (e: UnknownHostException) {
                false
            }
        }
    }

    override fun onActive() {
        super.onActive()
        connectivityManager.registerNetworkCallback(networkRequestBuilder(), networkStateObject)
        postValue(false) // Consider all networks "unavailable" on start
    }

    override fun onInactive() {
        super.onInactive()
        connectivityManager.unregisterNetworkCallback(networkStateObject)
    }

    private fun networkRequestBuilder(): NetworkRequest {
        return NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
            .build()
    }
}

The "disconnected" status due to the system switch on cellular from wifi will be transmitted through this LiveData class, there is nothing we can do about it. But once the cellular active, the current LiveData class will return the new network availability status (true).

Do not hesitate if you have questions or remarks.

Related