Skip to content

Commit

Permalink
fix: Ensure Connectivity on Android is correctly reported when lost
Browse files Browse the repository at this point in the history
  • Loading branch information
miquelbeltran committed Apr 11, 2024
1 parent 15985a1 commit 4d7d3f1
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 150 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,102 +8,118 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.os.Build;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;

import java.util.ArrayList;
import java.util.List;

/** Reports connectivity related information such as connectivity type and wifi information. */
/**
* Reports connectivity related information such as connectivity type and wifi information.
*/
public class Connectivity {
static final String CONNECTIVITY_NONE = "none";
static final String CONNECTIVITY_WIFI = "wifi";
static final String CONNECTIVITY_MOBILE = "mobile";
static final String CONNECTIVITY_ETHERNET = "ethernet";
static final String CONNECTIVITY_BLUETOOTH = "bluetooth";
static final String CONNECTIVITY_VPN = "vpn";
static final String CONNECTIVITY_OTHER = "other";
private final ConnectivityManager connectivityManager;
static final String CONNECTIVITY_NONE = "none";
static final String CONNECTIVITY_WIFI = "wifi";
static final String CONNECTIVITY_MOBILE = "mobile";
static final String CONNECTIVITY_ETHERNET = "ethernet";
static final String CONNECTIVITY_BLUETOOTH = "bluetooth";
static final String CONNECTIVITY_VPN = "vpn";
static final String CONNECTIVITY_OTHER = "other";
private final ConnectivityManager connectivityManager;

public Connectivity(ConnectivityManager connectivityManager) {
this.connectivityManager = connectivityManager;
}
public Connectivity(ConnectivityManager connectivityManager) {
this.connectivityManager = connectivityManager;
}

List<String> getNetworkTypes() {
List<String> types = new ArrayList<>();
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network network = connectivityManager.getActiveNetwork();
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
if (capabilities == null
|| !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
types.add(CONNECTIVITY_NONE);
return types;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|| capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)) {
types.add(CONNECTIVITY_WIFI);
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
types.add(CONNECTIVITY_ETHERNET);
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
types.add(CONNECTIVITY_VPN);
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
types.add(CONNECTIVITY_MOBILE);
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) {
types.add(CONNECTIVITY_BLUETOOTH);
}
if (types.isEmpty()
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
types.add(CONNECTIVITY_OTHER);
}
if (types.isEmpty()) {
types.add(CONNECTIVITY_NONE);
}
} else {
// For legacy versions, return a single type as before or adapt similarly if multiple types
// need to be supported
return getNetworkTypesLegacy();
List<String> getNetworkTypes() {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network network = connectivityManager.getActiveNetwork();
return getCapabilitiesFromNetwork(network);
} else {
// For legacy versions, return a single type as before or adapt similarly if multiple types
// need to be supported
return getNetworkTypesLegacy();
}
}

return types;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
List<String> getCapabilitiesFromNetwork(Network network) {
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
return getCapabilitiesList(capabilities);
}

@SuppressWarnings("deprecation")
private List<String> getNetworkTypesLegacy() {
// handle type for Android versions less than Android 6
android.net.NetworkInfo info = connectivityManager.getActiveNetworkInfo();
List<String> types = new ArrayList<>();
if (info == null || !info.isConnected()) {
types.add(CONNECTIVITY_NONE);
return types;
@NonNull
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
List<String> getCapabilitiesList(NetworkCapabilities capabilities) {
List<String> types = new ArrayList<>();
if (capabilities == null
|| !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
types.add(CONNECTIVITY_NONE);
return types;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|| capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)) {
types.add(CONNECTIVITY_WIFI);
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
types.add(CONNECTIVITY_ETHERNET);
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
types.add(CONNECTIVITY_VPN);
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
types.add(CONNECTIVITY_MOBILE);
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) {
types.add(CONNECTIVITY_BLUETOOTH);
}
if (types.isEmpty()
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
types.add(CONNECTIVITY_OTHER);
}
if (types.isEmpty()) {
types.add(CONNECTIVITY_NONE);
}
return types;
}
int type = info.getType();
switch (type) {
case ConnectivityManager.TYPE_BLUETOOTH:
types.add(CONNECTIVITY_BLUETOOTH);
break;
case ConnectivityManager.TYPE_ETHERNET:
types.add(CONNECTIVITY_ETHERNET);
break;
case ConnectivityManager.TYPE_WIFI:
case ConnectivityManager.TYPE_WIMAX:
types.add(CONNECTIVITY_WIFI);
break;
case ConnectivityManager.TYPE_VPN:
types.add(CONNECTIVITY_VPN);
break;
case ConnectivityManager.TYPE_MOBILE:
case ConnectivityManager.TYPE_MOBILE_DUN:
case ConnectivityManager.TYPE_MOBILE_HIPRI:
types.add(CONNECTIVITY_MOBILE);
break;
default:
types.add(CONNECTIVITY_OTHER);

@SuppressWarnings("deprecation")
private List<String> getNetworkTypesLegacy() {
// handle type for Android versions less than Android 6
android.net.NetworkInfo info = connectivityManager.getActiveNetworkInfo();
List<String> types = new ArrayList<>();
if (info == null || !info.isConnected()) {
types.add(CONNECTIVITY_NONE);
return types;
}
int type = info.getType();
switch (type) {
case ConnectivityManager.TYPE_BLUETOOTH:
types.add(CONNECTIVITY_BLUETOOTH);
break;
case ConnectivityManager.TYPE_ETHERNET:
types.add(CONNECTIVITY_ETHERNET);
break;
case ConnectivityManager.TYPE_WIFI:
case ConnectivityManager.TYPE_WIMAX:
types.add(CONNECTIVITY_WIFI);
break;
case ConnectivityManager.TYPE_VPN:
types.add(CONNECTIVITY_VPN);
break;
case ConnectivityManager.TYPE_MOBILE:
case ConnectivityManager.TYPE_MOBILE_DUN:
case ConnectivityManager.TYPE_MOBILE_HIPRI:
types.add(CONNECTIVITY_MOBILE);
break;
default:
types.add(CONNECTIVITY_OTHER);
}
return types;
}
return types;
}

public ConnectivityManager getConnectivityManager() {
return connectivityManager;
}
public ConnectivityManager getConnectivityManager() {
return connectivityManager;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

package dev.fluttercommunity.plus.connectivity;

import static dev.fluttercommunity.plus.connectivity.Connectivity.CONNECTIVITY_NONE;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
Expand All @@ -14,6 +16,9 @@
import android.os.Build;
import android.os.Handler;
import android.os.Looper;

import java.util.List;

import io.flutter.plugin.common.EventChannel;

/**
Expand All @@ -25,78 +30,81 @@
* to set up the receiver.
*/
public class ConnectivityBroadcastReceiver extends BroadcastReceiver
implements EventChannel.StreamHandler {
private final Context context;
private final Connectivity connectivity;
private EventChannel.EventSink events;
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private ConnectivityManager.NetworkCallback networkCallback;
public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
implements EventChannel.StreamHandler {
private final Context context;
private final Connectivity connectivity;
private EventChannel.EventSink events;
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private ConnectivityManager.NetworkCallback networkCallback;
public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";

public ConnectivityBroadcastReceiver(Context context, Connectivity connectivity) {
this.context = context;
this.connectivity = connectivity;
}
public ConnectivityBroadcastReceiver(Context context, Connectivity connectivity) {
this.context = context;
this.connectivity = connectivity;
}

@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
this.events = events;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
networkCallback =
new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
sendEvent();
}
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
this.events = events;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
networkCallback =
new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
// Use the provided Network object in the callback
sendEvent(connectivity.getCapabilitiesFromNetwork(network));
}

@Override
public void onCapabilitiesChanged(
Network network, NetworkCapabilities networkCapabilities) {
sendEvent();
}
@Override
public void onCapabilitiesChanged(
Network network, NetworkCapabilities networkCapabilities) {
// Use the provided NetworkCapabilities in the callback
sendEvent(connectivity.getCapabilitiesList(networkCapabilities));
}

@Override
public void onLost(Network network) {
sendEvent();
}
};
connectivity.getConnectivityManager().registerDefaultNetworkCallback(networkCallback);
} else {
context.registerReceiver(this, new IntentFilter(CONNECTIVITY_ACTION));
@Override
public void onLost(Network network) {
// Send NONE to ensure no network is reported when connectivity is lost
sendEvent(List.of(CONNECTIVITY_NONE));
}
};
connectivity.getConnectivityManager().registerDefaultNetworkCallback(networkCallback);
} else {
context.registerReceiver(this, new IntentFilter(CONNECTIVITY_ACTION));
}
// Need to emit first event with connectivity types without waiting for first change in system
// that might happen much later
sendEvent(connectivity.getNetworkTypes());
}
// Need to emit first event with connectivity types without waiting for first change in system
// that might happen much later
sendEvent();
}

@Override
public void onCancel(Object arguments) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (networkCallback != null) {
connectivity.getConnectivityManager().unregisterNetworkCallback(networkCallback);
networkCallback = null;
}
} else {
try {
context.unregisterReceiver(this);
} catch (Exception e) {
// listen never called, ignore the error
}
@Override
public void onCancel(Object arguments) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (networkCallback != null) {
connectivity.getConnectivityManager().unregisterNetworkCallback(networkCallback);
networkCallback = null;
}
} else {
try {
context.unregisterReceiver(this);
} catch (Exception e) {
// listen never called, ignore the error
}
}
}
}

@Override
public void onReceive(Context context, Intent intent) {
if (events != null) {
events.success(connectivity.getNetworkTypes());
@Override
public void onReceive(Context context, Intent intent) {
if (events != null) {
events.success(connectivity.getNetworkTypes());
}
}
}

private void sendEvent() {
Runnable runnable = () -> events.success(connectivity.getNetworkTypes());
// The dalay is needed because callback methods suffer from race conditions.
// More info:
// https://developer.android.com/develop/connectivity/network-ops/reading-network-state#listening-events
mainHandler.postDelayed(runnable, 100);
}
private void sendEvent(List<String> networkTypes) {
Runnable runnable = () -> events.success(networkTypes);
// The dalay is needed because callback methods suffer from race conditions.
// More info:
// https://developer.android.com/develop/connectivity/network-ops/reading-network-state#listening-events
mainHandler.postDelayed(runnable, 100);
}
}

0 comments on commit 4d7d3f1

Please sign in to comment.