Bind process to default network and ignore initial callback burst

Pin the process's outgoing sockets to the current default Android
Network via ConnectivityManager.bindProcessToNetwork so fresh dials
after a WiFi/cellular switch do not stall on TCP SYN retransmits
through the departing interface.

Skip the initial onAvailable burst fired right after registering the
NetworkCallback. That burst reflects current state, not a transition,
and was triggering a spurious EngineRestarter restart that cancelled
the in-flight login on cold start.
This commit is contained in:
Zoltán Papp
2026-04-20 15:24:41 +02:00
parent 6eb07a3495
commit f0df3f5986
2 changed files with 57 additions and 2 deletions
@@ -4,8 +4,13 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ConcreteNetworkAvailabilityListener implements NetworkAvailabilityListener {
// Grace window after subscribing a listener during which Android's initial
// onAvailable burst is treated as state seeding, not as a transition.
private static final long INITIAL_BURST_GRACE_MS = 3000;
private final Map<Integer, Boolean> availableNetworkTypes;
private NetworkToggleListener listener;
private volatile long listenerSubscribedAt = 0;
public ConcreteNetworkAvailabilityListener() {
this.availableNetworkTypes = new ConcurrentHashMap<>();
@@ -38,16 +43,27 @@ public class ConcreteNetworkAvailabilityListener implements NetworkAvailabilityL
}
private void notifyListener() {
if (listener != null) {
listener.onNetworkTypeChanged();
NetworkToggleListener l = listener;
if (l == null) {
return;
}
// Skip Android's initial onAvailable burst that fires right after the
// NetworkCallback is registered; that is the current state, not a
// transition, and must not trigger an engine restart.
long subscribedAt = listenerSubscribedAt;
if (subscribedAt != 0 && System.currentTimeMillis() - subscribedAt < INITIAL_BURST_GRACE_MS) {
return;
}
l.onNetworkTypeChanged();
}
public void subscribe(NetworkToggleListener listener) {
this.listener = listener;
this.listenerSubscribedAt = System.currentTimeMillis();
}
public void unsubscribe() {
this.listener = null;
this.listenerSubscribedAt = 0;
}
}
@@ -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<Integer> operation) {
@@ -58,10 +60,37 @@ public class NetworkChangeDetector {
};
}
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 class NetworkChangeDetector {
} 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) {