How to handle network change between wifi and mobile data?
Asked Answered
C

4

26

I am building a VoIP app.During a VoIP call when user switches between WiFi to mobile data i have a problem handling the scenario.

In my call screen activity I have registered for receiver which helps me get notified about the network change scenarios.

This is the code which I am using for detecting change in networks in the onRecieve Method. conn_name is private class level variable holding previous connection name.

ConnectivityManager connectivity_mgr = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));
NetworkInfo net_info = connectivity_mgr.getActiveNetworkInfo();

if (net_info != null && net_info.isConnectedOrConnecting() && !conn_name.equalsIgnoreCase("")) {
    new_con = net_info.getExtraInfo();
    if (new_con != null && !new_con.equalsIgnoreCase(conn_name))
        network_changed = true;
    conn_name = (new_con == null) ? "" : new_con;
    connectionStatus ="connected";
} else {
    if (net_info != null && conn_name.equalsIgnoreCase("")) {
        conn_name = net_info.getExtraInfo();
        connectionStatus ="connected";
        network_changed = true;
    } else if(!new_con.equals(conn_name)) {
        conn_name = "";
        connectionStatus ="disconnected";
        network_changed = true;
    }
}

So using above method I able detect network changes. But one peculiar thing happens when I am connected with WiFi. When my app starts initially it is connected with mobile data.when user enters into his known WiFi area,he gets connected to his known WiFi. Since WiFi is always chosen as default route,android switches to WiFi and I receive the network notification that WiFi has been turned on.

So I update my apps IP address to WiFi IP address, so no issues here. But still mobile data is still connected at the same time but getActiveNetworkInfo() tells me that I'm connected with WiFi clearly even if I was early connected to mobile data.

So the problem is when user switches off the WiFi button,and mobile data is still connected but I still receive the notification for WiFi turn off. It indicates me that network is disconnected even when my phone is still connected to mobile data.

But after a second I receive a notification that mobile data is connected. But once I receive the network disconnected, I have closed my VoIP call. So when I receive a notification for WiFi switched off how can I make sure whether mobile data is still connected.

I tried getActiveNetworkInfo() but it happens to be null when I receive notification for WiFi turned off.

I have followed this links:

Android API call to determine user setting "Data Enabled"
How to tell if 'Mobile Network Data' is enabled or disabled (even when connected by WiFi)?

Using the above link I am able detect that mobile data button has been enabled when user has connected with mobiledata.it gives me true. But the problem happens when this particular case happens.

Now when wifi is disabled I get notification but it shows that mobile data is disabled even when my mobile data is enabled. I am not able to handle this situation as I disconnect my calls when I receive my disconnected notification.

Crumley answered 3/1, 2018 at 15:25 Comment(2)
You can keep a pause of 1-2 seconds when the wifi is disconnected. And then after 1-2 second try rechecking if Mobile Data is available or not.Involuted
@Involuted yes if there isnt any solution im planning to go for thisCrumley
L
35

You can utilize APIs of ConnectivityManager: particularly in your use case you are interested in registerDefaultNetworkCallback():


    public class TestActivity extends AppCompatActivity {

        private ConnectivityManager manager;
        private final ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(Network network) {
                super.onAvailable(network);
                // this ternary operation is not quite true, because non-metered doesn't yet mean, that it's wifi
                // nevertheless, for simplicity let's assume that's true
                Log.i("vvv", "connected to " + (manager.isActiveNetworkMetered() ? "LTE" : "WIFI"));
            }

            @Override
            public void onLost(Network network) {
                super.onLost(network);
                Log.i("vvv", "losing active connection");
            }
        };

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            manager.registerDefaultNetworkCallback(networkCallback);
        }

        @Override
        protected void onDestroy() {
            super.onDestroy();
            manager.unregisterNetworkCallback(networkCallback);
        }
    }

My device connects to LTE in about half second.

enter image description here

This means, that you cannot know beforehand, whether device will eventually connect to LTE or no at the time, when WIFI gets disconnected. Thus, you can adopt following approach: post an action on a handler to happen in a second and within this action cancel the call. If connection appears anytime soon - unschedule previously posted action. If you end up being in Runnable code, then connection wasn't established quickly, which means, that you should end the call.


    public class TestActivity extends AppCompatActivity {

        private ConnectivityManager manager;

        private final Handler handler = new Handler();
        private final ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(Network network) {
                super.onAvailable(network);
                Log.i("vvv", "connected to " + (manager.isActiveNetworkMetered() ? "LTE" : "WIFI"));

                // we've got a connection, remove callbacks (if we have posted any)
                handler.removeCallbacks(endCall);
            }

            @Override
            public void onLost(Network network) {
                super.onLost(network);
                Log.i("vvv", "losing active connection");

                // Schedule an event to take place in a second
                handler.postDelayed(endCall, 1000);
            }
        };

        private final Runnable endCall = new Runnable() {
            @Override
            public void run() {
                // if execution has reached here - feel free to cancel the call
                // because no connection was established in a second
            }
        };

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            manager.registerDefaultNetworkCallback(networkCallback);
        }

        @Override
        protected void onDestroy() {
            super.onDestroy();
            manager.unregisterNetworkCallback(networkCallback);
            handler.removeCallbacks(endCall);
        }
    }

The downside of the approach is, that registerDefaultNetworkCallback() is available starting from API 24. There does not exist an alternative in ConnectivityManagerCompat either. Instead, you can use registerNetworkCallback() which is available from API 21.

Laurelaureano answered 18/1, 2018 at 18:6 Comment(2)
let me give a tryCrumley
Using registerDefaultNetworkCallback, you won't know before hand. Alternatively, you can register two separate callbacks, one for TRANSPORT_WIFI, and other for TRANSPORT_CELLULAR. That ways you will have reference to both Networks if they are available. Most of the OEMs always keep Cell network active in the background. In case they do not, you can request Cell network by using CM#requestNetwork.Azerbaijani
H
4

My implementation with RxJava

class ConnectivityMonitor : ConnectivityManager.NetworkCallback() {
    var networkTimeout: Disposable? = null

    override fun onAvailable(network: Network?) {
        super.onAvailable(network)
        Timber.d("Network available")
        networkTimeout?.dispose()
    }

    override fun onLosing(network: Network?, maxMsToLive: Int) {
        super.onLosing(network, maxMsToLive)
        Timber.d("onLosing")
    }

    override fun onLost(network: Network?) {
        super.onLost(network)
        Timber.d("onLost")

        networkTimeout = Single.timer(5, TimeUnit.SECONDS)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { _ -> Timber.d("Network lost") }
    }

    override fun onUnavailable() {
        super.onUnavailable()
        Timber.d("Network unavailable")
    }
}

Listener setup:

    private fun setupListeners() {
        // connection listener
        val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            connectivityManager.registerDefaultNetworkCallback(connectivityMonitor)
        } else {
            val builder = NetworkRequest.Builder()
            connectivityManager.registerNetworkCallback(builder.build(), connectivityMonitor)
        }
    }

The use of the timer / disposable allows for delays between switching connection types.

Heisser answered 10/4, 2019 at 17:4 Comment(0)
E
3

You can use BroadcastReceiver and register NETWORK_STATE_CHANGED_ACTION & WIFI_STATE_CHANGED_ACTION.

private boolean isConnected;

final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent == null || intent.getAction() == null)
            return;
        switch (intent.getAction()){
            case WifiManager.NETWORK_STATE_CHANGED_ACTION :
            case WifiManager.WIFI_STATE_CHANGED_ACTION :
                if (!isConnected && isOnline(BaseActivity.this)) {
                    isConnected = true;
                    // do stuff when connected
                    Log.i("Network status: ","Connected");
                }else{
                    isConnected = isOnline(BaseActivity.this);
                    Log.i("Network status: ","Disconnected");
                }
                break;
        }
    }
};



@Override
protected void onCreate(Bundle savedInstanceState) {
    isConnected = isOnline(this);
    final IntentFilter filters = new IntentFilter();
    filters.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    filters.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    registerReceiver(broadcastReceiver, filters);
}


public static boolean isOnline(Context ctx) {
    ConnectivityManager cm = (ConnectivityManager) ctx
            .getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo netInfo = cm != null
            ? cm.getActiveNetworkInfo()
            : null;
    return netInfo != null && netInfo.isConnectedOrConnecting();
}

Update Don't forget to unregisterReceiver BroadcastReceiver onDestroy

@Override
protected void onDestroy() {
    unregisterReceiver(broadcastReceiver);
    super.onDestroy();
} 
Ette answered 24/1, 2018 at 6:47 Comment(3)
wat happens when mobiledata is connected while receiving network notification for wifi turned downCrumley
@Crumley isConnected flag initialized inside onCreate method using online utility function, and isConnected flag will judge between old and current status onReceive action.Ette
@Crumley You can post your end call stuff using Handler with delay for switching between Wifi and mobile data, As @Laurelaureano answer mentioned.Ette
T
0

thanks for your contribution @azizbiken, I have implemented same thing in ProducerScope in compose

fun networkCallback(producer: ProducerScope<ConnectionState>, context: Context, callback: (ConnectionState) -> Unit): ConnectivityManager.NetworkCallback {
return object : ConnectivityManager.NetworkCallback() {
    var unAvailableJob: Job? = null
    override fun onAvailable(network: Network) {
        //cancel the coroutine once you get network instead of updating no network status
        unAvailableJob?.cancel()
            callback(ConnectionState.Available)
    }

    override fun onLost(network: Network) {
        unAvailableJob = producer.launch {
            delay(1000)
            callback(ConnectionState.Unavailable)
        }
    }
}
}

and here is the calling code

    val callback = networkCallback(this,context = applicationContext, callback = { connectionState -> trySend(connectionState) })
Teeny answered 24/11, 2022 at 11:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.