From 4d7d3f1ec73946ee088f0218164f7514a6620d56 Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Thu, 11 Apr 2024 07:39:23 +0200 Subject: [PATCH] fix: Ensure Connectivity on Android is correctly reported when lost --- .../plus/connectivity/Connectivity.java | 188 ++++++++++-------- .../ConnectivityBroadcastReceiver.java | 136 +++++++------ 2 files changed, 174 insertions(+), 150 deletions(-) diff --git a/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/Connectivity.java b/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/Connectivity.java index 2e54d37758..71b0ec99c0 100644 --- a/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/Connectivity.java +++ b/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/Connectivity.java @@ -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 getNetworkTypes() { - List 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 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 getCapabilitiesFromNetwork(Network network) { + NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); + return getCapabilitiesList(capabilities); + } - @SuppressWarnings("deprecation") - private List getNetworkTypesLegacy() { - // handle type for Android versions less than Android 6 - android.net.NetworkInfo info = connectivityManager.getActiveNetworkInfo(); - List types = new ArrayList<>(); - if (info == null || !info.isConnected()) { - types.add(CONNECTIVITY_NONE); - return types; + @NonNull + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + List getCapabilitiesList(NetworkCapabilities capabilities) { + List 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 getNetworkTypesLegacy() { + // handle type for Android versions less than Android 6 + android.net.NetworkInfo info = connectivityManager.getActiveNetworkInfo(); + List 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; + } } diff --git a/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/ConnectivityBroadcastReceiver.java b/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/ConnectivityBroadcastReceiver.java index 07a01796c1..a094790e2c 100644 --- a/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/ConnectivityBroadcastReceiver.java +++ b/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/ConnectivityBroadcastReceiver.java @@ -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; @@ -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; /** @@ -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 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); + } }