diff --git a/netbird b/netbird
index f01c1eea..5b09078d 160000
--- a/netbird
+++ b/netbird
@@ -1 +1 @@
-Subproject commit f01c1eea6ac803fff4678abe93f95b4c965e372b
+Subproject commit 5b09078da2ac14550741ce8731e7cf4b4a62a728
diff --git a/tool/src/main/java/io/netbird/client/tool/EngineRestarter.java b/tool/src/main/java/io/netbird/client/tool/EngineRestarter.java
index 23230adf..f8922a59 100644
--- a/tool/src/main/java/io/netbird/client/tool/EngineRestarter.java
+++ b/tool/src/main/java/io/netbird/client/tool/EngineRestarter.java
@@ -6,6 +6,7 @@
import android.util.Log;
import io.netbird.client.tool.networks.NetworkToggleListener;
+import io.netbird.gomobile.android.ConnectionListener;
/**
*
EngineRestarter restarts the Go engine.
@@ -52,6 +53,7 @@ private void restartEngine() {
if (isRestartInProgress) {
Log.e(LOGTAG, "engine restart timeout - forcing flag reset");
isRestartInProgress = false;
+ notifyDisconnected();
}
};
@@ -72,6 +74,7 @@ public void onStarted() {
@Override
public void onStopped() {
Log.d(LOGTAG, "engine is stopped, restarting...");
+ notifyConnecting();
engineRunner.runWithoutAuth();
}
@@ -81,6 +84,7 @@ public void onError(String msg) {
isRestartInProgress = false; // Resetting flag on error as well
handler.removeCallbacks(timeoutCallback); // Cancel timeout
engineRunner.removeServiceStateListener(this);
+ notifyDisconnected();
}
};
currentListener = serviceStateListener;
@@ -94,9 +98,34 @@ public void onError(String msg) {
}
Log.d(LOGTAG, "engine is running, stopping due to network change");
+ notifyConnecting();
engineRunner.stop();
}
+ private void notifyConnecting() {
+ ConnectionListener listener = engineRunner.getConnectionListener();
+ if (listener == null) {
+ return;
+ }
+ try {
+ listener.onConnecting();
+ } catch (Exception e) {
+ Log.w(LOGTAG, "onConnecting notification failed: " + e.getMessage());
+ }
+ }
+
+ private void notifyDisconnected() {
+ ConnectionListener listener = engineRunner.getConnectionListener();
+ if (listener == null) {
+ return;
+ }
+ try {
+ listener.onDisconnected();
+ } catch (Exception e) {
+ Log.w(LOGTAG, "onDisconnected notification failed: " + e.getMessage());
+ }
+ }
+
@Override
public void onNetworkTypeChanged() {
Log.d(LOGTAG, "network type changed, scheduling restart with "
diff --git a/tool/src/main/java/io/netbird/client/tool/EngineRunner.java b/tool/src/main/java/io/netbird/client/tool/EngineRunner.java
index dfdbf846..2638b28a 100644
--- a/tool/src/main/java/io/netbird/client/tool/EngineRunner.java
+++ b/tool/src/main/java/io/netbird/client/tool/EngineRunner.java
@@ -29,6 +29,7 @@ class EngineRunner {
private boolean engineIsRunning = false;
Set serviceStateListeners = ConcurrentHashMap.newKeySet();
private final Client goClient;
+ private ConnectionListener connectionListener;
public EngineRunner(Context context, NetworkChangeListener networkChangeListener, TunAdapter tunAdapter,
IFaceDiscover iFaceDiscover, String versionName, boolean isTraceLogEnabled, boolean isDebuggable,
@@ -124,13 +125,19 @@ public synchronized boolean isRunning() {
}
public synchronized void setConnectionListener(ConnectionListener listener) {
+ this.connectionListener = listener;
goClient.setConnectionListener(listener);
}
public synchronized void removeStatusListener() {
+ this.connectionListener = null;
goClient.removeConnectionListener();
}
+ synchronized ConnectionListener getConnectionListener() {
+ return connectionListener;
+ }
+
public synchronized void addServiceStateListener(ServiceStateListener serviceStateListener) {
if (engineIsRunning) {
serviceStateListener.onStarted();
diff --git a/tool/src/main/java/io/netbird/client/tool/ForegroundNotification.java b/tool/src/main/java/io/netbird/client/tool/ForegroundNotification.java
index 0161af9c..534dfe61 100644
--- a/tool/src/main/java/io/netbird/client/tool/ForegroundNotification.java
+++ b/tool/src/main/java/io/netbird/client/tool/ForegroundNotification.java
@@ -26,7 +26,9 @@ public void startForeground() {
NotificationChannel channel = new NotificationChannel(
channelId,
service.getResources().getString(R.string.fg_notification_channel_name),
- NotificationManager.IMPORTANCE_DEFAULT);
+ NotificationManager.IMPORTANCE_LOW);
+ channel.setSound(null, null);
+ channel.enableVibration(false);
((NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
Intent notificationIntent = new Intent();
diff --git a/tool/src/main/java/io/netbird/client/tool/VPNService.java b/tool/src/main/java/io/netbird/client/tool/VPNService.java
index 8494d48d..2dc5f12d 100644
--- a/tool/src/main/java/io/netbird/client/tool/VPNService.java
+++ b/tool/src/main/java/io/netbird/client/tool/VPNService.java
@@ -62,14 +62,15 @@ public void onCreate() {
// Create foreground notification before initializing engine
fgNotification = new ForegroundNotification(this);
- // Create network availability listener before initializing engine
- networkAvailabilityListener = new ConcreteNetworkAvailabilityListener();
-
-
engineRunner = new EngineRunner(this, notifier, tunAdapter, iFaceDiscover, versionName,
preferences.isTraceLogEnabled(), Version.isDebuggable(this), profileManager);
engineRunner.addServiceStateListener(serviceStateListener);
+ // Create network availability listener after the engine runner so we
+ // can gate notifications on the engine actually being up; this avoids
+ // acting on Android's initial onAvailable burst during cold start.
+ networkAvailabilityListener = new ConcreteNetworkAvailabilityListener(engineRunner::isRunning);
+
engineRestarter = new EngineRestarter(engineRunner);
networkAvailabilityListener.subscribe(engineRestarter);
diff --git a/tool/src/main/java/io/netbird/client/tool/networks/ConcreteNetworkAvailabilityListener.java b/tool/src/main/java/io/netbird/client/tool/networks/ConcreteNetworkAvailabilityListener.java
index 8c8cdbce..2957fb41 100644
--- a/tool/src/main/java/io/netbird/client/tool/networks/ConcreteNetworkAvailabilityListener.java
+++ b/tool/src/main/java/io/netbird/client/tool/networks/ConcreteNetworkAvailabilityListener.java
@@ -2,13 +2,24 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BooleanSupplier;
public class ConcreteNetworkAvailabilityListener implements NetworkAvailabilityListener {
private final Map availableNetworkTypes;
+ private final BooleanSupplier shouldNotify;
private NetworkToggleListener listener;
public ConcreteNetworkAvailabilityListener() {
+ this(() -> true);
+ }
+
+ // shouldNotify is consulted before each listener notification. Pass
+ // engineRunner::isRunning to swallow the initial onAvailable burst that
+ // fires right after registerNetworkCallback; until the engine is actually
+ // running there is nothing to restart.
+ public ConcreteNetworkAvailabilityListener(BooleanSupplier shouldNotify) {
this.availableNetworkTypes = new ConcurrentHashMap<>();
+ this.shouldNotify = shouldNotify;
}
@Override
@@ -38,9 +49,14 @@ public void onNetworkLost(@Constants.NetworkType int networkType) {
}
private void notifyListener() {
- if (listener != null) {
- listener.onNetworkTypeChanged();
+ NetworkToggleListener l = listener;
+ if (l == null) {
+ return;
+ }
+ if (!shouldNotify.getAsBoolean()) {
+ return;
}
+ l.onNetworkTypeChanged();
}
public void subscribe(NetworkToggleListener listener) {
diff --git a/tool/src/main/java/io/netbird/client/tool/networks/NetworkChangeDetector.java b/tool/src/main/java/io/netbird/client/tool/networks/NetworkChangeDetector.java
index f02a09c9..8fdceca3 100644
--- a/tool/src/main/java/io/netbird/client/tool/networks/NetworkChangeDetector.java
+++ b/tool/src/main/java/io/netbird/client/tool/networks/NetworkChangeDetector.java
@@ -13,11 +13,13 @@ public class NetworkChangeDetector {
private static final String LOGTAG = NetworkChangeDetector.class.getSimpleName();
private final ConnectivityManager connectivityManager;
private ConnectivityManager.NetworkCallback networkCallback;
+ private ConnectivityManager.NetworkCallback defaultNetworkCallback;
private volatile NetworkAvailabilityListener listener;
public NetworkChangeDetector(ConnectivityManager connectivityManager) {
this.connectivityManager = connectivityManager;
initNetworkCallback();
+ initDefaultNetworkCallback();
}
private void checkNetworkCapabilities(Network network, Consumer operation) {
@@ -58,10 +60,37 @@ public void onCapabilitiesChanged(@NonNull Network network, @NonNull NetworkCapa
};
}
+ private void initDefaultNetworkCallback() {
+ defaultNetworkCallback = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(@NonNull Network network) {
+ Log.d(LOGTAG, "default network became " + network + ", binding process to it");
+ try {
+ if (!connectivityManager.bindProcessToNetwork(network)) {
+ Log.w(LOGTAG, "bindProcessToNetwork returned false for " + network);
+ }
+ } catch (Exception e) {
+ Log.e(LOGTAG, "bindProcessToNetwork failed", e);
+ }
+ }
+
+ @Override
+ public void onLost(@NonNull Network network) {
+ Log.d(LOGTAG, "default network " + network + " lost, clearing process binding");
+ try {
+ connectivityManager.bindProcessToNetwork(null);
+ } catch (Exception e) {
+ Log.e(LOGTAG, "bindProcessToNetwork(null) failed", e);
+ }
+ }
+ };
+ }
+
public void registerNetworkCallback() {
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
connectivityManager.registerNetworkCallback(builder.build(), networkCallback);
+ connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback);
}
public void unregisterNetworkCallback() {
@@ -70,6 +99,16 @@ public void unregisterNetworkCallback() {
} catch (Exception e) {
Log.e(LOGTAG, "failed to unregister network callback", e);
}
+ try {
+ connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
+ } catch (Exception e) {
+ Log.e(LOGTAG, "failed to unregister default network callback", e);
+ }
+ try {
+ connectivityManager.bindProcessToNetwork(null);
+ } catch (Exception e) {
+ Log.e(LOGTAG, "bindProcessToNetwork(null) on unregister failed", e);
+ }
}
public void subscribe(NetworkAvailabilityListener listener) {
diff --git a/tool/src/test/java/io/netbird/client/tool/ConcreteNetworkAvailabilityListenerUnitTest.java b/tool/src/test/java/io/netbird/client/tool/ConcreteNetworkAvailabilityListenerUnitTest.java
index 809ceb11..e1ea8c95 100644
--- a/tool/src/test/java/io/netbird/client/tool/ConcreteNetworkAvailabilityListenerUnitTest.java
+++ b/tool/src/test/java/io/netbird/client/tool/ConcreteNetworkAvailabilityListenerUnitTest.java
@@ -29,7 +29,7 @@ public void deactivateMobile() {
this.listener.onNetworkLost(Constants.NetworkType.MOBILE);
}
}
-
+
private static class MockNetworkToggleListener implements NetworkToggleListener {
private int totalTimesNetworkTypeChanged = 0;
@@ -47,7 +47,7 @@ public void resetCounter() {
public void shouldNotifyListenerNetworkUpgraded() {
// Assemble:
var networkToggleListener = new MockNetworkToggleListener();
- var networkAvailabilityListener = new ConcreteNetworkAvailabilityListener();
+ var networkAvailabilityListener = new ConcreteNetworkAvailabilityListener(() -> true);
networkAvailabilityListener.subscribe(networkToggleListener);
var networkChangeDetector = new MockNetworkChangeDetector(networkAvailabilityListener);
@@ -64,7 +64,7 @@ public void shouldNotifyListenerNetworkUpgraded() {
public void shouldNotifyListenerNetworkDowngraded() {
// Assemble:
var networkToggleListener = new MockNetworkToggleListener();
- var networkAvailabilityListener = new ConcreteNetworkAvailabilityListener();
+ var networkAvailabilityListener = new ConcreteNetworkAvailabilityListener(() -> true);
networkAvailabilityListener.subscribe(networkToggleListener);
var networkChangeDetector = new MockNetworkChangeDetector(networkAvailabilityListener);
@@ -82,7 +82,7 @@ public void shouldNotifyListenerNetworkDowngraded() {
public void shouldNotNotifyListenerNetworkDidNotUpgrade() {
// Assemble:
var networkToggleListener = new MockNetworkToggleListener();
- var networkAvailabilityListener = new ConcreteNetworkAvailabilityListener();
+ var networkAvailabilityListener = new ConcreteNetworkAvailabilityListener(() -> true);
networkAvailabilityListener.subscribe(networkToggleListener);
var networkChangeDetector = new MockNetworkChangeDetector(networkAvailabilityListener);
@@ -103,7 +103,7 @@ public void shouldNotNotifyListenerNetworkDidNotUpgrade() {
public void shouldNotNotifyListenerNoNetworksAvailable() {
// Assemble:
var networkToggleListener = new MockNetworkToggleListener();
- var networkAvailabilityListener = new ConcreteNetworkAvailabilityListener();
+ var networkAvailabilityListener = new ConcreteNetworkAvailabilityListener(() -> true);
networkAvailabilityListener.subscribe(networkToggleListener);
var networkChangeDetector = new MockNetworkChangeDetector(networkAvailabilityListener);
@@ -118,4 +118,23 @@ public void shouldNotNotifyListenerNoNetworksAvailable() {
// Assert:
assertEquals(0, networkToggleListener.totalTimesNetworkTypeChanged);
}
+
+ @Test
+ public void shouldNotNotifyListenerWhenEngineNotRunning() {
+ // Assemble: engine never running, so initial onAvailable burst from
+ // Android must not trigger a restart.
+ var networkToggleListener = new MockNetworkToggleListener();
+ var networkAvailabilityListener = new ConcreteNetworkAvailabilityListener(() -> false);
+ networkAvailabilityListener.subscribe(networkToggleListener);
+
+ var networkChangeDetector = new MockNetworkChangeDetector(networkAvailabilityListener);
+
+ // Act:
+ networkChangeDetector.activateMobile();
+ networkChangeDetector.activateWifi();
+ networkChangeDetector.deactivateWifi();
+
+ // Assert:
+ assertEquals(0, networkToggleListener.totalTimesNetworkTypeChanged);
+ }
}