From 51b8c0722004a2a67ee0bbb3ecbe8983bf160119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Tue, 14 Apr 2026 18:38:32 +0200 Subject: [PATCH 1/6] Pass app cache directory to Go for debug bundle temp files Pass context.getCacheDir() through AndroidPlatformFiles so the Go debug bundle generator can create temporary zip files in a writable directory instead of /data/local/tmp/. --- netbird | 2 +- .../io/netbird/client/tool/AndroidPlatformFiles.java | 9 ++++++++- .../main/java/io/netbird/client/tool/EngineRunner.java | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/netbird b/netbird index 4eed459f..b3178255 160000 --- a/netbird +++ b/netbird @@ -1 +1 @@ -Subproject commit 4eed459f27bd7e90faa7fe99e1edf4a59dc71265 +Subproject commit b3178255c0a3c487e114e282593d45ed5f147d3e diff --git a/tool/src/main/java/io/netbird/client/tool/AndroidPlatformFiles.java b/tool/src/main/java/io/netbird/client/tool/AndroidPlatformFiles.java index cbac637b..b65e2440 100644 --- a/tool/src/main/java/io/netbird/client/tool/AndroidPlatformFiles.java +++ b/tool/src/main/java/io/netbird/client/tool/AndroidPlatformFiles.java @@ -5,10 +5,12 @@ public class AndroidPlatformFiles implements PlatformFiles { private final String configurationFilePath; private final String stateFilePath; + private final String cacheDir; - public AndroidPlatformFiles(String configurationFilePath, String stateFilePath) { + public AndroidPlatformFiles(String configurationFilePath, String stateFilePath, String cacheDir) { this.configurationFilePath = configurationFilePath; this.stateFilePath = stateFilePath; + this.cacheDir = cacheDir; } @Override @@ -20,4 +22,9 @@ public String configurationFilePath() { public String stateFilePath() { return stateFilePath; } + + @Override + public String cacheDir() { + return cacheDir; + } } 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 703cbd4d..66719918 100644 --- a/tool/src/main/java/io/netbird/client/tool/EngineRunner.java +++ b/tool/src/main/java/io/netbird/client/tool/EngineRunner.java @@ -91,7 +91,7 @@ private synchronized void runClient(@Nullable URLOpener urlOpener, boolean isAnd // Create fresh PlatformFiles with current config/state paths // This allows profile switching without recreating the entire Client - var platformFiles = new AndroidPlatformFiles(configurationFilePath, stateFilePath); + var platformFiles = new AndroidPlatformFiles(configurationFilePath, stateFilePath, context.getCacheDir().getAbsolutePath()); Log.d(LOGTAG, "Running engine with config: " + configurationFilePath + ", state: " + stateFilePath); try { From 92acfab574a1e070bcbae9031b643e50bca001bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Tue, 14 Apr 2026 18:50:30 +0200 Subject: [PATCH 2/6] Update netbird submodule with logcat debug bundle support --- netbird | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbird b/netbird index b3178255..1d792f0b 160000 --- a/netbird +++ b/netbird @@ -1 +1 @@ -Subproject commit b3178255c0a3c487e114e282593d45ed5f147d3e +Subproject commit 1d792f0b532ea96b926c166e77fe345cdd512b5c From 401ec499d255e9bf0468ad78fd6a3a6e91064e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Tue, 14 Apr 2026 19:19:57 +0200 Subject: [PATCH 3/6] Add Troubleshoot fragment with debug bundle upload Move trace log toggle and log sharing from Advanced to a new Troubleshoot fragment accessible via the drawer menu. Replace the old logcat share with debug bundle generation and upload that copies the upload key to clipboard. Add anonymize toggle. Works with or without a running engine. --- .../java/io/netbird/client/MainActivity.java | 8 ++ .../io/netbird/client/ServiceAccessor.java | 2 + .../client/ui/advanced/AdvancedFragment.java | 36 ------- .../ui/troubleshoot/TroubleshootFragment.java | 90 ++++++++++++++++ .../res/drawable/ic_menu_troubleshoot.xml | 11 ++ app/src/main/res/layout/fragment_advanced.xml | 59 +--------- .../main/res/layout/fragment_troubleshoot.xml | 102 ++++++++++++++++++ .../main/res/menu/activity_main_drawer.xml | 16 +-- .../main/res/navigation/mobile_navigation.xml | 6 ++ app/src/main/res/values/strings.xml | 3 + netbird | 2 +- .../io/netbird/client/tool/EngineRunner.java | 8 ++ .../io/netbird/client/tool/VPNService.java | 4 + 13 files changed, 246 insertions(+), 101 deletions(-) create mode 100644 app/src/main/java/io/netbird/client/ui/troubleshoot/TroubleshootFragment.java create mode 100644 app/src/main/res/drawable/ic_menu_troubleshoot.xml create mode 100644 app/src/main/res/layout/fragment_troubleshoot.xml diff --git a/app/src/main/java/io/netbird/client/MainActivity.java b/app/src/main/java/io/netbird/client/MainActivity.java index b8fd1517..c7fb0b54 100644 --- a/app/src/main/java/io/netbird/client/MainActivity.java +++ b/app/src/main/java/io/netbird/client/MainActivity.java @@ -413,6 +413,14 @@ public void deselectRoute(String route) throws Exception { mBinder.deselectRoute(route); } + @Override + public String debugBundle(boolean anonymize) throws Exception { + if (mBinder == null) { + throw new Exception("VPN service not connected"); + } + return mBinder.debugBundle(anonymize); + } + @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 bf17e443..d37d399c 100644 --- a/app/src/main/java/io/netbird/client/ServiceAccessor.java +++ b/app/src/main/java/io/netbird/client/ServiceAccessor.java @@ -17,4 +17,6 @@ public interface ServiceAccessor { void addRouteChangeListener(RouteChangeListener listener); void removeRouteChangeListener(RouteChangeListener listener); + + String debugBundle(boolean anonymize) throws Exception; } \ 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 ed060c57..3f1e2a7c 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 @@ -1,6 +1,5 @@ package io.netbird.client.ui.advanced; -import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; @@ -20,7 +19,6 @@ import io.netbird.client.R; import io.netbird.client.databinding.ComponentSwitchBinding; import io.netbird.client.databinding.FragmentAdvancedBinding; -import io.netbird.client.tool.Logcat; import io.netbird.client.tool.Preferences; import io.netbird.client.tool.ProfileManagerWrapper; @@ -103,28 +101,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, setPreSharedKey(presharedKey, inflater.getContext()); }); - // Enable trace logs Preferences preferences = new Preferences(inflater.getContext()); - binding.switchTraceLog.setChecked(preferences.isTraceLogEnabled()); - - // Handle trace log switch toggle - binding.switchTraceLog.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked) { - preferences.enableTraceLog(); - } else { - preferences.disableTraceLog(); - } - }); - - // Make parent layout clickable to toggle switch (for TV remote) - binding.traceLogLayout.setOnClickListener(v -> { - binding.switchTraceLog.toggle(); - }); - - // Handle "Share Logs" button click - binding.buttonShareLogs.setOnClickListener(v -> { - shareLog(); - }); // Rosenpass settings try { @@ -362,17 +339,4 @@ private boolean hasPreSharedKey(Context context) { } } - private void shareLog() { - Activity activity = getActivity(); - if (activity == null) { - return; - } - - try { - Logcat logcat = new Logcat(activity); - logcat.dump(); - } catch (Exception e) { - Log.e(LOGTAG, "failed to dump log", e); - } - } } diff --git a/app/src/main/java/io/netbird/client/ui/troubleshoot/TroubleshootFragment.java b/app/src/main/java/io/netbird/client/ui/troubleshoot/TroubleshootFragment.java new file mode 100644 index 00000000..a3f6aae9 --- /dev/null +++ b/app/src/main/java/io/netbird/client/ui/troubleshoot/TroubleshootFragment.java @@ -0,0 +1,90 @@ +package io.netbird.client.ui.troubleshoot; + +import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import io.netbird.client.R; +import io.netbird.client.ServiceAccessor; +import io.netbird.client.databinding.FragmentTroubleshootBinding; +import io.netbird.client.tool.Preferences; + +public class TroubleshootFragment extends Fragment { + + private static final String LOGTAG = "TroubleshootFragment"; + private FragmentTroubleshootBinding binding; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + binding = FragmentTroubleshootBinding.inflate(inflater, container, false); + + Preferences preferences = new Preferences(inflater.getContext()); + binding.switchTraceLog.setChecked(preferences.isTraceLogEnabled()); + + binding.switchTraceLog.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + preferences.enableTraceLog(); + } else { + preferences.disableTraceLog(); + } + }); + + binding.traceLogLayout.setOnClickListener(v -> { + binding.switchTraceLog.toggle(); + }); + + binding.anonymizeLayout.setOnClickListener(v -> { + binding.switchAnonymize.toggle(); + }); + + binding.buttonDebugBundle.setOnClickListener(v -> { + generateDebugBundle(); + }); + + return binding.getRoot(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + + private void generateDebugBundle() { + Activity activity = getActivity(); + if (activity == null || !(activity instanceof ServiceAccessor)) { + return; + } + + boolean anonymize = binding.switchAnonymize.isChecked(); + binding.buttonDebugBundle.setEnabled(false); + new Thread(() -> { + try { + String key = ((ServiceAccessor) activity).debugBundle(anonymize); + activity.runOnUiThread(() -> { + binding.buttonDebugBundle.setEnabled(true); + ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("Debug bundle key", key); + clipboard.setPrimaryClip(clip); + Toast.makeText(activity, "Debug bundle key copied to clipboard", Toast.LENGTH_SHORT).show(); + }); + } catch (Exception e) { + Log.e(LOGTAG, "failed to create debug bundle", e); + activity.runOnUiThread(() -> { + binding.buttonDebugBundle.setEnabled(true); + Toast.makeText(activity, "Failed to create debug bundle: " + e.getMessage(), Toast.LENGTH_LONG).show(); + }); + } + }).start(); + } +} diff --git a/app/src/main/res/drawable/ic_menu_troubleshoot.xml b/app/src/main/res/drawable/ic_menu_troubleshoot.xml new file mode 100644 index 00000000..9856cc1e --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_troubleshoot.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/layout/fragment_advanced.xml b/app/src/main/res/layout/fragment_advanced.xml index c25aa711..ad46aea4 100644 --- a/app/src/main/res/layout/fragment_advanced.xml +++ b/app/src/main/res/layout/fragment_advanced.xml @@ -76,63 +76,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/preshared_key" /> - - - - - - - - - -