From 9ae32bc56b02f7141b5f0de7dabf16cf81f54263 Mon Sep 17 00:00:00 2001
From: Michael Uray <25169478+MichaelUray@users.noreply.github.com>
Date: Sat, 25 Apr 2026 22:30:37 +0000
Subject: [PATCH 1/3] Add Lazy connection toggle to Advanced settings
Adds a switch in the Advanced fragment that controls the
LazyConnectionEnabled engine setting. Default is OFF so existing
behavior is preserved; users opt in to per-peer connection on first
traffic for reduced background load.
The toggle wires through the new GetLazyConnectionEnabled /
SetLazyConnectionEnabled methods on the gomobile Preferences bridge.
Tested on a Galaxy S21 (Android 15): toggling the switch persists
LazyConnectionEnabled in the config; on reconnect the engine activates
the lazy connection manager (setup lazy connection service, per-peer
activity listeners on 127.0.0.1:N, 15-minute inactivity threshold) and
peers are wired up on demand instead of eagerly.
---
.../client/ui/advanced/AdvancedFragment.java | 18 ++++++-
app/src/main/res/layout/fragment_advanced.xml | 51 ++++++++++++++++++-
app/src/main/res/values/strings.xml | 2 +
3 files changed, 68 insertions(+), 3 deletions(-)
diff --git a/app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java b/app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java
index 3f1e2a7c..ba968d38 100644
--- a/app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java
+++ b/app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java
@@ -197,6 +197,7 @@ private void initializeEngineConfigSwitches() {
binding.switchDisableFirewall.setChecked(goPreferences.getDisableFirewall());
binding.switchAllowSsh.setChecked(goPreferences.getServerSSHAllowed());
binding.switchBlockInbound.setChecked(goPreferences.getBlockInbound());
+ binding.switchLazyConnection.setChecked(goPreferences.getLazyConnectionEnabled());
// Set up change listeners
binding.switchDisableClientRoutes.setOnCheckedChangeListener((buttonView, isChecked) -> {
@@ -252,15 +253,28 @@ private void initializeEngineConfigSwitches() {
Log.e(LOGTAG, "Failed to set block inbound", e);
}
});
-
+
+ binding.switchLazyConnection.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ try {
+ goPreferences.setLazyConnectionEnabled(isChecked);
+ goPreferences.commit();
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Failed to set lazy connection", e);
+ }
+ });
+
// Make parent layouts clickable to toggle switches (for TV remote)
binding.layoutAllowSsh.setOnClickListener(v -> {
binding.switchAllowSsh.toggle();
});
-
+
binding.layoutBlockInbound.setOnClickListener(v -> {
binding.switchBlockInbound.toggle();
});
+
+ binding.layoutLazyConnection.setOnClickListener(v -> {
+ binding.switchLazyConnection.toggle();
+ });
binding.layoutDisableClientRoutes.setOnClickListener(v -> {
binding.switchDisableClientRoutes.toggle();
diff --git a/app/src/main/res/layout/fragment_advanced.xml b/app/src/main/res/layout/fragment_advanced.xml
index ad46aea4..082f918e 100644
--- a/app/src/main/res/layout/fragment_advanced.xml
+++ b/app/src/main/res/layout/fragment_advanced.xml
@@ -268,7 +268,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Allows SSH connections to this device
Block inbound connections
Blocks all inbound connections to this device and routed networks. This overrides any policies received from the management service
+ Lazy connection
+ Defer per-peer connection setup until first actual traffic. Reduces background load but adds latency to the first packet to a peer
NetBird logo
From 4f2c2f881669fb8967c32f5b5f5047308b0eae14 Mon Sep 17 00:00:00 2001
From: Michael Uray <25169478+MichaelUray@users.noreply.github.com>
Date: Sat, 2 May 2026 14:30:59 +0000
Subject: [PATCH 2/3] ui/advanced: replace Lazy switch with Connection-Mode
picker + 3 timeouts
Phase 3.7h Android counterpart of the Fyne Network-tab UI:
- Spinner: Follow server / relay-forced / p2p / p2p-lazy / p2p-dynamic
- 3 EditTexts (Relay/P2P/P2P-RetryMax timeout) shown per-mode appropriately
- Themed spinner item layout (fixes white-on-white in dark mode)
- "currently: " suffix on Follow-server entry shows server-pushed value
- Per-field hint shows actual server-default seconds
- ServerPushed* values via ServiceAccessor -> EngineRunner -> connMgr
Bumps netbird submodule to include the new server-pushed RPC fields and
ConnMgr.ServerPushed* getters.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
.../java/io/netbird/client/MainActivity.java | 26 +++
.../io/netbird/client/ServiceAccessor.java | 27 +++
.../client/ui/advanced/AdvancedFragment.java | 219 ++++++++++++++++--
app/src/main/res/layout/fragment_advanced.xml | 107 ++++++---
.../main/res/layout/spinner_item_themed.xml | 7 +
.../main/res/values/connection_mode_array.xml | 13 ++
app/src/main/res/values/strings.xml | 6 +
netbird | 2 +-
.../io/netbird/client/tool/EngineRunner.java | 24 ++
.../io/netbird/client/tool/VPNService.java | 16 ++
10 files changed, 406 insertions(+), 41 deletions(-)
create mode 100644 app/src/main/res/layout/spinner_item_themed.xml
create mode 100644 app/src/main/res/values/connection_mode_array.xml
diff --git a/app/src/main/java/io/netbird/client/MainActivity.java b/app/src/main/java/io/netbird/client/MainActivity.java
index c7fb0b54..7f15bfaa 100644
--- a/app/src/main/java/io/netbird/client/MainActivity.java
+++ b/app/src/main/java/io/netbird/client/MainActivity.java
@@ -421,6 +421,32 @@ public String debugBundle(boolean anonymize) throws Exception {
return mBinder.debugBundle(anonymize);
}
+ @Override
+ public String getServerPushedConnectionMode() {
+ if (mBinder == null) {
+ return "";
+ }
+ return mBinder.getServerPushedConnectionMode();
+ }
+
+ @Override
+ public long getServerPushedRelayTimeoutSecs() {
+ if (mBinder == null) return 0;
+ return mBinder.getServerPushedRelayTimeoutSecs();
+ }
+
+ @Override
+ public long getServerPushedP2pTimeoutSecs() {
+ if (mBinder == null) return 0;
+ return mBinder.getServerPushedP2pTimeoutSecs();
+ }
+
+ @Override
+ public long getServerPushedP2pRetryMaxSecs() {
+ if (mBinder == null) return 0;
+ return mBinder.getServerPushedP2pRetryMaxSecs();
+ }
+
@Override
public void addRouteChangeListener(RouteChangeListener listener) {
if (mBinder == null) {
diff --git a/app/src/main/java/io/netbird/client/ServiceAccessor.java b/app/src/main/java/io/netbird/client/ServiceAccessor.java
index d37d399c..f4126884 100644
--- a/app/src/main/java/io/netbird/client/ServiceAccessor.java
+++ b/app/src/main/java/io/netbird/client/ServiceAccessor.java
@@ -19,4 +19,31 @@ public interface ServiceAccessor {
void removeRouteChangeListener(RouteChangeListener listener);
String debugBundle(boolean anonymize) throws Exception;
+
+ /**
+ * Returns the canonical name (e.g. "p2p-dynamic") of the connection
+ * mode the management server most recently pushed. Empty string when
+ * the engine has not connected yet or no value has been pushed --
+ * the UI should then hide the "(currently: ...)" suffix on the
+ * Follow-server entry of the override dropdown.
+ */
+ String getServerPushedConnectionMode();
+
+ /**
+ * Returns the relay timeout (seconds) the management server most
+ * recently pushed. 0 when not yet known. Used as a hint in the
+ * override field so the user can see the value they are about to
+ * override.
+ */
+ long getServerPushedRelayTimeoutSecs();
+
+ /**
+ * Returns the p2p (ICE-only) timeout in seconds most recently pushed.
+ */
+ long getServerPushedP2pTimeoutSecs();
+
+ /**
+ * Returns the p2p retry-max cap in seconds most recently pushed.
+ */
+ long getServerPushedP2pRetryMaxSecs();
}
\ No newline at end of file
diff --git a/app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java b/app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java
index ba968d38..9233ab9d 100644
--- a/app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java
+++ b/app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java
@@ -3,10 +3,15 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
@@ -197,7 +202,12 @@ private void initializeEngineConfigSwitches() {
binding.switchDisableFirewall.setChecked(goPreferences.getDisableFirewall());
binding.switchAllowSsh.setChecked(goPreferences.getServerSSHAllowed());
binding.switchBlockInbound.setChecked(goPreferences.getBlockInbound());
- binding.switchLazyConnection.setChecked(goPreferences.getLazyConnectionEnabled());
+
+ // Connection mode + timeouts (Phase 3.7h Android UI). Default
+ // selection is "Follow server" (index 0 in connection_mode_entries),
+ // which clears any local override. Selecting an explicit mode
+ // unhides the timeout fields below.
+ initializeConnectionModeUI();
// Set up change listeners
binding.switchDisableClientRoutes.setOnCheckedChangeListener((buttonView, isChecked) -> {
@@ -254,15 +264,6 @@ private void initializeEngineConfigSwitches() {
}
});
- binding.switchLazyConnection.setOnCheckedChangeListener((buttonView, isChecked) -> {
- try {
- goPreferences.setLazyConnectionEnabled(isChecked);
- goPreferences.commit();
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to set lazy connection", e);
- }
- });
-
// Make parent layouts clickable to toggle switches (for TV remote)
binding.layoutAllowSsh.setOnClickListener(v -> {
binding.switchAllowSsh.toggle();
@@ -271,10 +272,6 @@ private void initializeEngineConfigSwitches() {
binding.layoutBlockInbound.setOnClickListener(v -> {
binding.switchBlockInbound.toggle();
});
-
- binding.layoutLazyConnection.setOnClickListener(v -> {
- binding.switchLazyConnection.toggle();
- });
binding.layoutDisableClientRoutes.setOnClickListener(v -> {
binding.switchDisableClientRoutes.toggle();
@@ -297,6 +294,200 @@ private void initializeEngineConfigSwitches() {
}
}
+ /**
+ * Mapping from spinner position to canonical connection-mode string.
+ * Index 0 = "Follow server" -> empty string clears the local override
+ * so the daemon uses the server-pushed value. Other entries set an
+ * explicit local override that wins over the server value.
+ *
+ * Order matches connection_mode_entries (res/values/connection_mode_array.xml):
+ * Follow server, relay-forced, p2p, p2p-lazy, p2p-dynamic.
+ */
+ private static final String[] CONNECTION_MODE_VALUES = new String[] {
+ "", // 0: Follow server
+ "relay-forced", // 1
+ "p2p", // 2
+ "p2p-lazy", // 3
+ "p2p-dynamic" // 4
+ };
+
+ private void initializeConnectionModeUI() {
+ try {
+ // Build a theme-aware adapter so the dropdown uses our nb_txt color
+ // and the popup picks up nb_bg in dark mode.
+ // The "Follow server" entry gets a "(currently: )" suffix
+ // that surfaces the value the management server most recently
+ // pushed -- refreshed on every spinner-touch in case the engine
+ // was not connected yet when the fragment first opened.
+ refreshConnectionModeAdapter();
+
+ // Hydrate spinner from current persisted local override.
+ String currentMode = goPreferences.getConnectionMode();
+ int selectedIdx = 0;
+ for (int i = 0; i < CONNECTION_MODE_VALUES.length; i++) {
+ if (CONNECTION_MODE_VALUES[i].equals(currentMode)) {
+ selectedIdx = i;
+ break;
+ }
+ }
+ binding.spinnerConnectionMode.setSelection(selectedIdx);
+ updateTimeoutsVisibility(selectedIdx);
+
+ // Hydrate timeout fields with the locally-stored override (if any).
+ // Empty when no override is set so the user sees the field is
+ // currently inactive; the hint text shows the server-pushed
+ // default for that field as guidance.
+ long relay = goPreferences.getRelayTimeoutSeconds();
+ long p2p = goPreferences.getP2pTimeoutSeconds();
+ long retry = goPreferences.getP2pRetryMaxSeconds();
+ binding.editRelayTimeout.setText(relay == 0 ? "" : String.valueOf(relay));
+ binding.editP2pTimeout.setText(p2p == 0 ? "" : String.valueOf(p2p));
+ binding.editP2pRetryMax.setText(retry == 0 ? "" : String.valueOf(retry));
+
+ // Refresh the "(currently: ...)" suffix every time the spinner is
+ // touched. Cheap (just a getter call to the engine), and covers
+ // the case where the user opens this fragment before the daemon
+ // received its first PeerConfig.
+ binding.spinnerConnectionMode.setOnTouchListener((v, event) -> {
+ if (event.getActionMasked() == android.view.MotionEvent.ACTION_DOWN) {
+ refreshConnectionModeAdapter();
+ }
+ return false;
+ });
+
+ binding.spinnerConnectionMode.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ try {
+ goPreferences.setConnectionMode(CONNECTION_MODE_VALUES[position]);
+ goPreferences.commit();
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Failed to set connection mode", e);
+ }
+ updateTimeoutsVisibility(position);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) { }
+ });
+
+ wireTimeoutEditOnBlur(binding.editRelayTimeout, "relay", v -> {
+ try { goPreferences.setRelayTimeoutSeconds(v); goPreferences.commit(); }
+ catch (Exception e) { Log.e(LOGTAG, "Failed to set relay timeout", e); }
+ });
+ wireTimeoutEditOnBlur(binding.editP2pTimeout, "p2p", v -> {
+ try { goPreferences.setP2pTimeoutSeconds(v); goPreferences.commit(); }
+ catch (Exception e) { Log.e(LOGTAG, "Failed to set p2p timeout", e); }
+ });
+ wireTimeoutEditOnBlur(binding.editP2pRetryMax, "p2pRetryMax", v -> {
+ try { goPreferences.setP2pRetryMaxSeconds(v); goPreferences.commit(); }
+ catch (Exception e) { Log.e(LOGTAG, "Failed to set p2p retry max", e); }
+ });
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Failed to initialize connection mode UI", e);
+ }
+ }
+
+ private void refreshConnectionModeAdapter() {
+ if (binding == null) return;
+ String[] base = getResources().getStringArray(R.array.connection_mode_entries);
+ String[] entries = base.clone();
+ String pushed = "";
+ try {
+ if (requireActivity() instanceof io.netbird.client.ServiceAccessor) {
+ pushed = ((io.netbird.client.ServiceAccessor) requireActivity())
+ .getServerPushedConnectionMode();
+ }
+ } catch (Throwable t) {
+ Log.d(LOGTAG, "no server-pushed mode available yet: " + t.getMessage());
+ }
+ if (pushed != null && !pushed.isEmpty()) {
+ entries[0] = base[0] + " (currently: " + pushed + ")";
+ } else {
+ entries[0] = base[0] + " (engine not yet connected)";
+ }
+ int currentSelection = binding.spinnerConnectionMode.getSelectedItemPosition();
+ ArrayAdapter adapter = new ArrayAdapter<>(
+ requireContext(), R.layout.spinner_item_themed, entries);
+ adapter.setDropDownViewResource(R.layout.spinner_item_themed);
+ binding.spinnerConnectionMode.setAdapter(adapter);
+ if (currentSelection >= 0 && currentSelection < entries.length) {
+ binding.spinnerConnectionMode.setSelection(currentSelection);
+ }
+ // Server-pushed timeout values may have changed too; refresh hints.
+ refreshTimeoutHints();
+ }
+
+ private void updateTimeoutsVisibility(int spinnerPosition) {
+ // Inactivity timeouts only apply when the lazy/dynamic connection
+ // manager is active. Mapping (CONNECTION_MODE_VALUES indices):
+ // 0 follow-server : hide all (server may push any mode; default off)
+ // 1 relay-forced : hide all (relay tunnel always up, no teardown)
+ // 2 p2p : hide all (no inactivity manager runs)
+ // 3 p2p-lazy : show relay_timeout (whole-peer teardown)
+ // 4 p2p-dynamic : show all three (ICE-only + relay + retry-cap)
+ boolean lazyActive = (spinnerPosition == 3 || spinnerPosition == 4);
+ boolean dynamicActive = (spinnerPosition == 4);
+
+ binding.layoutTimeoutsContainer.setVisibility(lazyActive ? View.VISIBLE : View.GONE);
+ if (!lazyActive) return;
+
+ // relay timeout shown for both p2p-lazy and p2p-dynamic.
+ binding.labelP2pTimeout.setVisibility(dynamicActive ? View.VISIBLE : View.GONE);
+ binding.editP2pTimeout.setVisibility(dynamicActive ? View.VISIBLE : View.GONE);
+ binding.labelP2pRetryMax.setVisibility(dynamicActive ? View.VISIBLE : View.GONE);
+ binding.editP2pRetryMax.setVisibility(dynamicActive ? View.VISIBLE : View.GONE);
+
+ // Refresh hint text from the latest server-pushed values so users
+ // see what they would inherit if they leave a field blank.
+ refreshTimeoutHints();
+ }
+
+ private void refreshTimeoutHints() {
+ long relayServer = 0, p2pServer = 0, retryServer = 0;
+ try {
+ if (requireActivity() instanceof io.netbird.client.ServiceAccessor) {
+ io.netbird.client.ServiceAccessor sa =
+ (io.netbird.client.ServiceAccessor) requireActivity();
+ relayServer = sa.getServerPushedRelayTimeoutSecs();
+ p2pServer = sa.getServerPushedP2pTimeoutSecs();
+ retryServer = sa.getServerPushedP2pRetryMaxSecs();
+ }
+ } catch (Throwable t) {
+ Log.d(LOGTAG, "server-pushed timeouts unavailable: " + t.getMessage());
+ }
+ binding.editRelayTimeout.setHint(formatHint(relayServer));
+ binding.editP2pTimeout.setHint(formatHint(p2pServer));
+ binding.editP2pRetryMax.setHint(formatHint(retryServer));
+ }
+
+ private static String formatHint(long secs) {
+ if (secs <= 0) {
+ return "use server default";
+ }
+ return "use server default (" + secs + "s)";
+ }
+
+ private interface LongConsumer { void accept(long v); }
+
+ private void wireTimeoutEditOnBlur(EditText edit, String label, LongConsumer onCommit) {
+ edit.setOnFocusChangeListener((view, hasFocus) -> {
+ if (hasFocus) return;
+ String s = edit.getText().toString().trim();
+ long val = 0;
+ if (!s.isEmpty()) {
+ try { val = Long.parseLong(s); }
+ catch (NumberFormatException nfe) {
+ Log.w(LOGTAG, "Invalid " + label + " timeout: " + s);
+ edit.setText("");
+ return;
+ }
+ if (val < 0) val = 0;
+ }
+ onCommit.accept(val);
+ });
+ }
+
@Override
public void onDestroyView() {
super.onDestroyView();
diff --git a/app/src/main/res/layout/fragment_advanced.xml b/app/src/main/res/layout/fragment_advanced.xml
index 082f918e..4d552907 100644
--- a/app/src/main/res/layout/fragment_advanced.xml
+++ b/app/src/main/res/layout/fragment_advanced.xml
@@ -268,52 +268,107 @@
-
-
-
+ android:text="@string/advanced_connection_mode"
+ android:textColor="@color/nb_txt_light" />
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/layout_connection_mode">
+
diff --git a/app/src/main/res/values/connection_mode_array.xml b/app/src/main/res/values/connection_mode_array.xml
new file mode 100644
index 00000000..abb7fafd
--- /dev/null
+++ b/app/src/main/res/values/connection_mode_array.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ - Follow server
+ - relay-forced
+ - p2p
+ - p2p-lazy
+ - p2p-dynamic
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9db4f747..a5781028 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -98,6 +98,12 @@
Blocks all inbound connections to this device and routed networks. This overrides any policies received from the management service
Lazy connection
Defer per-peer connection setup until first actual traffic. Reduces background load but adds latency to the first packet to a peer
+ Connection mode
+ Override the connection mode pushed by the server. Leave on \"Follow server\" to use the server-configured value. Other modes activate the timeout fields below.
+ Relay timeout (seconds; leave empty to use server default)
+ P2P (ICE) timeout (seconds; leave empty to use server default)
+ P2P retry-max cap (seconds; leave empty to use server default)
+ use server default
NetBird logo
diff --git a/netbird b/netbird
index 5a89e662..83f8f01a 160000
--- a/netbird
+++ b/netbird
@@ -1 +1 @@
-Subproject commit 5a89e6621bf41cb6cb0da3040ffe68ae790e3c02
+Subproject commit 83f8f01ad8841441ef86f0cbb27eb11319bb928a
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..68940849 100644
--- a/tool/src/main/java/io/netbird/client/tool/EngineRunner.java
+++ b/tool/src/main/java/io/netbird/client/tool/EngineRunner.java
@@ -214,6 +214,30 @@ public void renewTUN(int fd) {
}
}
+ public String getServerPushedConnectionMode() {
+ try {
+ return goClient.getServerPushedConnectionMode();
+ } catch (Throwable t) {
+ android.util.Log.d(LOGTAG, "getServerPushedConnectionMode unavailable: " + t.getMessage());
+ return "";
+ }
+ }
+
+ public long getServerPushedRelayTimeoutSecs() {
+ try { return goClient.getServerPushedRelayTimeoutSecs(); }
+ catch (Throwable t) { return 0; }
+ }
+
+ public long getServerPushedP2pTimeoutSecs() {
+ try { return goClient.getServerPushedP2pTimeoutSecs(); }
+ catch (Throwable t) { return 0; }
+ }
+
+ public long getServerPushedP2pRetryMaxSecs() {
+ try { return goClient.getServerPushedP2pRetryMaxSecs(); }
+ catch (Throwable t) { return 0; }
+ }
+
public String debugBundle(boolean anonymize) throws Exception {
String configPath = profileManager.getActiveConfigPath();
String statePath = profileManager.getActiveStateFilePath();
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..af9077db 100644
--- a/tool/src/main/java/io/netbird/client/tool/VPNService.java
+++ b/tool/src/main/java/io/netbird/client/tool/VPNService.java
@@ -243,6 +243,22 @@ public String debugBundle(boolean anonymize) throws Exception {
return engineRunner.debugBundle(anonymize);
}
+ public String getServerPushedConnectionMode() {
+ return engineRunner.getServerPushedConnectionMode();
+ }
+
+ public long getServerPushedRelayTimeoutSecs() {
+ return engineRunner.getServerPushedRelayTimeoutSecs();
+ }
+
+ public long getServerPushedP2pTimeoutSecs() {
+ return engineRunner.getServerPushedP2pTimeoutSecs();
+ }
+
+ public long getServerPushedP2pRetryMaxSecs() {
+ return engineRunner.getServerPushedP2pRetryMaxSecs();
+ }
+
public void selectRoute(String route) throws Exception {
engineRunner.selectRoute(route);
}
From 77bc67fd73d334d25636972456c104cd10390f0f Mon Sep 17 00:00:00 2001
From: Michael Uray <25169478+MichaelUray@users.noreply.github.com>
Date: Sun, 3 May 2026 10:09:48 +0000
Subject: [PATCH 3/3] android: remove Force-Relay switch (replaced by
Connection-Mode picker)
Phase 3.7h finalisation. The standalone "Force relay connection" toggle
in Advanced Settings is removed because the new Connection-Mode picker
(landed earlier on this branch) supersedes it. Preferences key and
EnvVarPackager NB_FORCE_RELAY plumbing are removed too so the setting
no longer leaks into NetBird via env vars. Bumps netbird submodule to
include the matching ServerPushed-getter commits from the netbird repo.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
.../client/ui/advanced/AdvancedFragment.java | 36 -------------------
app/src/main/res/layout/fragment_advanced.xml | 13 +------
app/src/main/res/values/strings.xml | 2 --
netbird | 2 +-
.../netbird/client/tool/EnvVarPackager.java | 7 +---
.../io/netbird/client/tool/Preferences.java | 14 --------
6 files changed, 3 insertions(+), 71 deletions(-)
diff --git a/app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java b/app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java
index 9233ab9d..6593899e 100644
--- a/app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java
+++ b/app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java
@@ -17,12 +17,10 @@
import android.widget.Toast;
import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.fragment.app.Fragment;
import io.netbird.client.R;
-import io.netbird.client.databinding.ComponentSwitchBinding;
import io.netbird.client.databinding.FragmentAdvancedBinding;
import io.netbird.client.tool.Preferences;
import io.netbird.client.tool.ProfileManagerWrapper;
@@ -36,38 +34,6 @@ public class AdvancedFragment extends Fragment {
private FragmentAdvancedBinding binding;
private io.netbird.gomobile.android.Preferences goPreferences;
- private void showReconnectionNeededWarningDialog() {
- final View dialogView = getLayoutInflater().inflate(R.layout.dialog_simple_alert_message, null);
- final AlertDialog alertDialog = new AlertDialog.Builder(requireContext(), R.style.AlertDialogTheme)
- .setView(dialogView)
- .create();
-
- ((TextView)dialogView.findViewById(R.id.txt_dialog)).setText(R.string.reconnectionNeededWarningMessage);
- dialogView.findViewById(R.id.btn_ok_dialog).setOnClickListener(v -> alertDialog.dismiss());
- alertDialog.show();
- }
-
- private void configureForceRelayConnectionSwitch(@NonNull ComponentSwitchBinding binding, @NonNull Preferences preferences) {
- binding.switchTitle.setText(R.string.advanced_force_relay_conn);
- binding.switchDescription.setText(R.string.advanced_force_relay_conn_desc);
-
- binding.switchControl.setChecked(preferences.isConnectionForceRelayed());
- binding.switchControl.setOnCheckedChangeListener((buttonView, isChecked) -> {
- if (isChecked) {
- preferences.enableForcedRelayConnection();
- } else {
- preferences.disableForcedRelayConnection();
- }
-
- showReconnectionNeededWarningDialog();
- });
-
- // Make parent layout clickable to toggle switch (for TV remote)
- binding.getRoot().setOnClickListener(v -> {
- binding.switchControl.toggle();
- });
- }
-
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
@@ -163,8 +129,6 @@ public View onCreateView(@NonNull LayoutInflater inflater,
binding.switchRosenpassPermissive.toggle();
});
- configureForceRelayConnectionSwitch(binding.layoutForceRelayConnection, preferences);
-
// Initialize engine config switches (your settings)
initializeEngineConfigSwitches();
diff --git a/app/src/main/res/layout/fragment_advanced.xml b/app/src/main/res/layout/fragment_advanced.xml
index 4d552907..7d3b507a 100644
--- a/app/src/main/res/layout/fragment_advanced.xml
+++ b/app/src/main/res/layout/fragment_advanced.xml
@@ -567,17 +567,6 @@
-
-
+ app:layout_constraintTop_toBottomOf="@id/layout_disable_firewall">
Dark
Light
Choose the app appearance mode.
- Force relay connection
- Forces usage of relay when connecting to peers
exclamation mark
To apply the setting, you will need to reconnect.
Using setup keys for user devices is not recommended. SSO with MFA provides stronger security, proper user-device association, and periodic re-authentication.
diff --git a/netbird b/netbird
index 83f8f01a..c90d008b 160000
--- a/netbird
+++ b/netbird
@@ -1 +1 @@
-Subproject commit 83f8f01ad8841441ef86f0cbb27eb11319bb928a
+Subproject commit c90d008ba7514f1cfe9cd769fcaf8065e025ea73
diff --git a/tool/src/main/java/io/netbird/client/tool/EnvVarPackager.java b/tool/src/main/java/io/netbird/client/tool/EnvVarPackager.java
index f6413e99..3ba7cb2a 100644
--- a/tool/src/main/java/io/netbird/client/tool/EnvVarPackager.java
+++ b/tool/src/main/java/io/netbird/client/tool/EnvVarPackager.java
@@ -1,14 +1,9 @@
package io.netbird.client.tool;
-import io.netbird.gomobile.android.Android;
import io.netbird.gomobile.android.EnvList;
public class EnvVarPackager {
public static EnvList getEnvironmentVariables(Preferences preferences) {
- var envList = new EnvList();
-
- envList.put(Android.getEnvKeyNBForceRelay(), String.valueOf(preferences.isConnectionForceRelayed()));
-
- return envList;
+ return new EnvList();
}
}
diff --git a/tool/src/main/java/io/netbird/client/tool/Preferences.java b/tool/src/main/java/io/netbird/client/tool/Preferences.java
index ddf57ca5..5de30663 100644
--- a/tool/src/main/java/io/netbird/client/tool/Preferences.java
+++ b/tool/src/main/java/io/netbird/client/tool/Preferences.java
@@ -7,8 +7,6 @@ public class Preferences {
private final String keyTraceLog = "tracelog";
- private final String keyForceRelayConnection = "isConnectionForceRelayed";
-
private final SharedPreferences sharedPref;
public Preferences(Context context) {
@@ -26,18 +24,6 @@ public void disableTraceLog() {
sharedPref.edit().putBoolean(keyTraceLog, false).apply();
}
- public boolean isConnectionForceRelayed() {
- return sharedPref.getBoolean(keyForceRelayConnection, true);
- }
-
- public void enableForcedRelayConnection() {
- sharedPref.edit().putBoolean(keyForceRelayConnection, true).apply();
- }
-
- public void disableForcedRelayConnection() {
- sharedPref.edit().putBoolean(keyForceRelayConnection, false).apply();
- }
-
public static String defaultServer() {
return "https://api.netbird.io";
}