diff --git a/mobile/library/common/internal_engine.h b/mobile/library/common/internal_engine.h index 7541e88373f5..2c0323cfb75c 100644 --- a/mobile/library/common/internal_engine.h +++ b/mobile/library/common/internal_engine.h @@ -114,7 +114,7 @@ class InternalEngine : public Logger::Loggable { void onDefaultNetworkAvailable(); /** - * This function does the following when the default network configuration was changed. + * This function does the following when the default network was changed. * * - Sets the preferred network. * - Check for IPv6 connectivity. If there is no IPv6 no connectivity, it will call diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/AndroidNetworkMonitor.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/AndroidNetworkMonitor.java index 708ab67b640f..33e4292baf01 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/AndroidNetworkMonitor.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/AndroidNetworkMonitor.java @@ -25,7 +25,7 @@ *
  • When the internet is not available: call the * InternalEngine::onDefaultNetworkUnavailable callback.
  • * - *
  • When the capabilities are changed: call the + *
  • When the network is changed: call the * EnvoyEngine::onDefaultNetworkChanged.
  • * */ @@ -57,10 +57,20 @@ public static void shutdown() { instance = null; } - private static class DefaultNetworkCallback extends NetworkCallback { + @VisibleForTesting + static class DefaultNetworkCallback extends NetworkCallback { + private static final int[] TRANSPORT_TYPES = new int[] { + NetworkCapabilities.TRANSPORT_CELLULAR, NetworkCapabilities.TRANSPORT_WIFI, + NetworkCapabilities.TRANSPORT_BLUETOOTH, NetworkCapabilities.TRANSPORT_ETHERNET, + NetworkCapabilities.TRANSPORT_VPN, NetworkCapabilities.TRANSPORT_WIFI_AWARE, + NetworkCapabilities.TRANSPORT_LOWPAN, + }; + private static final int EMPTY_TRANSPORT_TYPE = -1; + private final EnvoyEngine envoyEngine; + @VisibleForTesting int transportType = EMPTY_TRANSPORT_TYPE; - private DefaultNetworkCallback(EnvoyEngine envoyEngine) { this.envoyEngine = envoyEngine; } + DefaultNetworkCallback(EnvoyEngine envoyEngine) { this.envoyEngine = envoyEngine; } @Override public void onAvailable(@NonNull Network network) { @@ -70,13 +80,17 @@ public void onAvailable(@NonNull Network network) { @Override public void onCapabilitiesChanged(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) { - if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { - if (networkCapabilities.hasCapability(NetworkCapabilities.TRANSPORT_WIFI)) { - envoyEngine.onDefaultNetworkChanged(EnvoyNetworkType.WLAN); - } else if (networkCapabilities.hasCapability(NetworkCapabilities.TRANSPORT_CELLULAR)) { - envoyEngine.onDefaultNetworkChanged(EnvoyNetworkType.WWAN); - } else { - envoyEngine.onDefaultNetworkChanged(EnvoyNetworkType.GENERIC); + // `onCapabilities` is guaranteed to be called immediately after `onAvailable` + // starting with Android O, so this logic may not work on older Android versions. + // https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onCapabilitiesChanged(android.net.Network,%20android.net.NetworkCapabilities) + if (transportType == EMPTY_TRANSPORT_TYPE) { + // The network was lost previously, see `onLost`. + onDefaultNetworkChanged(networkCapabilities); + } else { + // Only call the `onDefaultNetworkChanged` callback when there is a change in the + // transport type. + if (!networkCapabilities.hasTransport(transportType)) { + onDefaultNetworkChanged(networkCapabilities); } } } @@ -84,6 +98,30 @@ public void onCapabilitiesChanged(@NonNull Network network, @Override public void onLost(@NonNull Network network) { envoyEngine.onDefaultNetworkUnavailable(); + transportType = EMPTY_TRANSPORT_TYPE; + } + + private static int getTransportType(NetworkCapabilities networkCapabilities) { + for (int type : TRANSPORT_TYPES) { + if (networkCapabilities.hasTransport(type)) { + return type; + } + } + return EMPTY_TRANSPORT_TYPE; + } + + private void onDefaultNetworkChanged(NetworkCapabilities networkCapabilities) { + if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)) { + envoyEngine.onDefaultNetworkChanged(EnvoyNetworkType.WLAN); + } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + envoyEngine.onDefaultNetworkChanged(EnvoyNetworkType.WWAN); + } else { + envoyEngine.onDefaultNetworkChanged(EnvoyNetworkType.GENERIC); + } + } + transportType = getTransportType(networkCapabilities); } } diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngine.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngine.java index 4726662535ed..5de84632160d 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngine.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngine.java @@ -68,7 +68,7 @@ public interface EnvoyEngine { void onDefaultNetworkAvailable(); /** - * A callback into the Envoy Engine when the default network configuration was changed. + * A callback into the Envoy Engine when the default network type was changed. */ void onDefaultNetworkChanged(EnvoyNetworkType network); diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java index 01f1704370c8..49b93102c0b3 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java @@ -230,7 +230,7 @@ protected static native int registerStringAccessor(String accessorName, protected static native void onDefaultNetworkAvailable(long engine); /** - * A callback into the Envoy Engine when the default network configuration was changed. + * A callback into the Envoy Engine when the default network was changed. */ protected static native void onDefaultNetworkChanged(long engine, int networkType); diff --git a/mobile/test/java/io/envoyproxy/envoymobile/engine/AndroidNetworkMonitorTest.java b/mobile/test/java/io/envoyproxy/envoymobile/engine/AndroidNetworkMonitorTest.java index ef1e4e799b1b..bd433ce92269 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/engine/AndroidNetworkMonitorTest.java +++ b/mobile/test/java/io/envoyproxy/envoymobile/engine/AndroidNetworkMonitorTest.java @@ -1,7 +1,9 @@ package io.envoyproxy.envoymobile.engine; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.robolectric.Shadows.shadowOf; @@ -94,7 +96,7 @@ public void testOnDefaultNetworkChangedWlan() { shadowOf(connectivityManager).getNetworkCallbacks().forEach(callback -> { NetworkCapabilities capabilities = ShadowNetworkCapabilities.newInstance(); shadowOf(capabilities).addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); - shadowOf(capabilities).addCapability(NetworkCapabilities.TRANSPORT_WIFI); + shadowOf(capabilities).addTransportType(NetworkCapabilities.TRANSPORT_WIFI); callback.onCapabilitiesChanged(ShadowNetwork.newInstance(0), capabilities); }); @@ -106,7 +108,7 @@ public void testOnDefaultNetworkChangedWwan() { shadowOf(connectivityManager).getNetworkCallbacks().forEach(callback -> { NetworkCapabilities capabilities = ShadowNetworkCapabilities.newInstance(); shadowOf(capabilities).addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); - shadowOf(capabilities).addCapability(NetworkCapabilities.TRANSPORT_CELLULAR); + shadowOf(capabilities).addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); callback.onCapabilitiesChanged(ShadowNetwork.newInstance(0), capabilities); }); @@ -118,9 +120,109 @@ public void testOnDefaultNetworkChangedGeneric() { shadowOf(connectivityManager).getNetworkCallbacks().forEach(callback -> { NetworkCapabilities capabilities = ShadowNetworkCapabilities.newInstance(); shadowOf(capabilities).addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + shadowOf(capabilities).addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET); callback.onCapabilitiesChanged(ShadowNetwork.newInstance(0), capabilities); }); verify(mockEnvoyEngine).onDefaultNetworkChanged(EnvoyNetworkType.GENERIC); } + + @Test + public void testOnCapabilitiesChangedPreviousNetworkIsEmptyCallbackIsCalledWlan() { + AndroidNetworkMonitor.DefaultNetworkCallback callback = + new AndroidNetworkMonitor.DefaultNetworkCallback(mockEnvoyEngine); + NetworkCapabilities capabilities = ShadowNetworkCapabilities.newInstance(); + shadowOf(capabilities).addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + shadowOf(capabilities).addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + callback.onCapabilitiesChanged(ShadowNetwork.newInstance(0), capabilities); + + verify(mockEnvoyEngine).onDefaultNetworkChanged(EnvoyNetworkType.WLAN); + } + + @Test + public void testOnCapabilitiesChangedPreviousNetworkIsEmptyCallbackIsCalledWwan() { + AndroidNetworkMonitor.DefaultNetworkCallback callback = + new AndroidNetworkMonitor.DefaultNetworkCallback(mockEnvoyEngine); + NetworkCapabilities capabilities = ShadowNetworkCapabilities.newInstance(); + shadowOf(capabilities).addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + shadowOf(capabilities).addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + callback.onCapabilitiesChanged(ShadowNetwork.newInstance(0), capabilities); + + verify(mockEnvoyEngine).onDefaultNetworkChanged(EnvoyNetworkType.WWAN); + } + + @Test + public void testOnCapabilitiesChangedPreviousNetworkIsEmptyCallbackIsCalledGeneric() { + AndroidNetworkMonitor.DefaultNetworkCallback callback = + new AndroidNetworkMonitor.DefaultNetworkCallback(mockEnvoyEngine); + NetworkCapabilities capabilities = ShadowNetworkCapabilities.newInstance(); + shadowOf(capabilities).addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + shadowOf(capabilities).addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET); + callback.onCapabilitiesChanged(ShadowNetwork.newInstance(0), capabilities); + + verify(mockEnvoyEngine).onDefaultNetworkChanged(EnvoyNetworkType.GENERIC); + } + + @Test + public void testOnCapabilitiesChangedPreviousNetworkNotEmptyCallbackIsCalledWwan() { + AndroidNetworkMonitor.DefaultNetworkCallback callback = + new AndroidNetworkMonitor.DefaultNetworkCallback(mockEnvoyEngine); + callback.transportType = NetworkCapabilities.TRANSPORT_WIFI; + NetworkCapabilities capabilities = ShadowNetworkCapabilities.newInstance(); + shadowOf(capabilities).addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + shadowOf(capabilities).addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + callback.onCapabilitiesChanged(ShadowNetwork.newInstance(0), capabilities); + + verify(mockEnvoyEngine).onDefaultNetworkChanged(EnvoyNetworkType.WWAN); + } + + @Test + public void testOnCapabilitiesChangedPreviousNetworkNotEmptyCallCallbackIsCalledWlan() { + AndroidNetworkMonitor.DefaultNetworkCallback callback = + new AndroidNetworkMonitor.DefaultNetworkCallback(mockEnvoyEngine); + callback.transportType = NetworkCapabilities.TRANSPORT_CELLULAR; + NetworkCapabilities capabilities = ShadowNetworkCapabilities.newInstance(); + shadowOf(capabilities).addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + shadowOf(capabilities).addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + callback.onCapabilitiesChanged(ShadowNetwork.newInstance(0), capabilities); + + verify(mockEnvoyEngine).onDefaultNetworkChanged(EnvoyNetworkType.WLAN); + } + + @Test + public void testOnCapabilitiesChangedPreviousNetworkNotEmptyCallbackIsCalledGeneric() { + AndroidNetworkMonitor.DefaultNetworkCallback callback = + new AndroidNetworkMonitor.DefaultNetworkCallback(mockEnvoyEngine); + callback.transportType = NetworkCapabilities.TRANSPORT_BLUETOOTH; + NetworkCapabilities capabilities = ShadowNetworkCapabilities.newInstance(); + shadowOf(capabilities).addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + shadowOf(capabilities).addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET); + callback.onCapabilitiesChanged(ShadowNetwork.newInstance(0), capabilities); + + verify(mockEnvoyEngine).onDefaultNetworkChanged(EnvoyNetworkType.GENERIC); + } + + @Test + public void testOnCapabilitiesChangedPreviousNetworkNotEmptyCallbackIsNotCalled() { + AndroidNetworkMonitor.DefaultNetworkCallback callback = + new AndroidNetworkMonitor.DefaultNetworkCallback(mockEnvoyEngine); + callback.transportType = NetworkCapabilities.TRANSPORT_WIFI; + NetworkCapabilities capabilities = ShadowNetworkCapabilities.newInstance(); + shadowOf(capabilities).addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + shadowOf(capabilities).addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + callback.onCapabilitiesChanged(ShadowNetwork.newInstance(0), capabilities); + + verify(mockEnvoyEngine, never()).onDefaultNetworkChanged(any()); + } + + @Test + public void testOnCapabilitiesChangedNoInternetCallbackIsNotCalled() { + AndroidNetworkMonitor.DefaultNetworkCallback callback = + new AndroidNetworkMonitor.DefaultNetworkCallback(mockEnvoyEngine); + NetworkCapabilities capabilities = ShadowNetworkCapabilities.newInstance(); + shadowOf(capabilities).addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + callback.onCapabilitiesChanged(ShadowNetwork.newInstance(0), capabilities); + + verify(mockEnvoyEngine, never()).onDefaultNetworkChanged(any()); + } }