Skip to content

Add Android debug bundle support with Troubleshoot UI#163

Merged
pappz merged 6 commits intomainfrom
fix/android-debug-bundle
Apr 20, 2026
Merged

Add Android debug bundle support with Troubleshoot UI#163
pappz merged 6 commits intomainfrom
fix/android-debug-bundle

Conversation

@pappz
Copy link
Copy Markdown
Collaborator

@pappz pappz commented Apr 14, 2026

Add Android debug bundle support with Troubleshoot UI

  • Fix debug bundle generation on Android by passing the app's cache directory for temp files (os.CreateTemp was failing with permission denied on /data/local/tmp/)
  • Add logcat log collection for Android debug bundles via platform-dispatched addPlatformLog() — dumps the logcat buffer into the zip archive
  • Add DebugBundle() method to the Android Go client that generates a bundle, uploads it, and returns the upload key — works with or without a running engine
  • Add new Troubleshoot fragment in the drawer menu with:
    • Trace log level toggle (moved from Advanced)
    • Anonymize sensitive data toggle
    • Upload debug bundle button (copies upload key to clipboard)

Summary by CodeRabbit

  • New Features

    • Added Troubleshoot menu section with debug bundle generation functionality.
    • Added anonymization toggle for debug bundles to protect sensitive data.
  • Refactor

    • Relocated trace logging controls from Advanced to Troubleshoot section.
    • Removed Share Logs button from Advanced settings.

pappz added 3 commits April 14, 2026 18:38
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/.
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.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

Warning

Rate limit exceeded

@pappz has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 24 minutes and 35 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 24 minutes and 35 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bdab7891-3be8-4800-832f-3a948ebdc978

📥 Commits

Reviewing files that changed from the base of the PR and between 401ec49 and 84ca392.

⛔ Files ignored due to path filters (20)
  • app/src/main/res/drawable-hdpi/ic_menu_about.png is excluded by !**/*.png
  • app/src/main/res/drawable-hdpi/ic_menu_advanced.png is excluded by !**/*.png
  • app/src/main/res/drawable-hdpi/ic_menu_change_server.png is excluded by !**/*.png
  • app/src/main/res/drawable-hdpi/ic_menu_docs.png is excluded by !**/*.png
  • app/src/main/res/drawable-mdpi/ic_menu_about.png is excluded by !**/*.png
  • app/src/main/res/drawable-mdpi/ic_menu_advanced.png is excluded by !**/*.png
  • app/src/main/res/drawable-mdpi/ic_menu_change_server.png is excluded by !**/*.png
  • app/src/main/res/drawable-mdpi/ic_menu_docs.png is excluded by !**/*.png
  • app/src/main/res/drawable-xhdpi/ic_menu_about.png is excluded by !**/*.png
  • app/src/main/res/drawable-xhdpi/ic_menu_advanced.png is excluded by !**/*.png
  • app/src/main/res/drawable-xhdpi/ic_menu_change_server.png is excluded by !**/*.png
  • app/src/main/res/drawable-xhdpi/ic_menu_docs.png is excluded by !**/*.png
  • app/src/main/res/drawable-xxhdpi/ic_menu_about.png is excluded by !**/*.png
  • app/src/main/res/drawable-xxhdpi/ic_menu_advanced.png is excluded by !**/*.png
  • app/src/main/res/drawable-xxhdpi/ic_menu_change_server.png is excluded by !**/*.png
  • app/src/main/res/drawable-xxhdpi/ic_menu_docs.png is excluded by !**/*.png
  • app/src/main/res/drawable-xxxhdpi/ic_menu_about.png is excluded by !**/*.png
  • app/src/main/res/drawable-xxxhdpi/ic_menu_advanced.png is excluded by !**/*.png
  • app/src/main/res/drawable-xxxhdpi/ic_menu_change_server.png is excluded by !**/*.png
  • app/src/main/res/drawable-xxxhdpi/ic_menu_docs.png is excluded by !**/*.png
📒 Files selected for processing (7)
  • app/src/main/java/io/netbird/client/ui/troubleshoot/TroubleshootFragment.java
  • app/src/main/res/drawable/ic_menu_about.xml
  • app/src/main/res/drawable/ic_menu_advanced.xml
  • app/src/main/res/drawable/ic_menu_change_server.xml
  • app/src/main/res/drawable/ic_menu_docs.xml
  • netbird
  • tool/src/main/java/io/netbird/client/tool/EngineRunner.java
📝 Walkthrough

Walkthrough

A new "Troubleshoot" feature is introduced with a dedicated fragment, relocating trace-log configuration from Advanced settings. A debugBundle() method is exposed across the service layer (MainActivity → ServiceAccessor → VPNService → EngineRunner) to generate debug bundles with optional anonymization, backed by underlying Go client functionality. Navigation menu and resources are updated accordingly.

Changes

Cohort / File(s) Summary
Debug Bundle API Chain
app/src/main/java/io/netbird/client/MainActivity.java, app/src/main/java/io/netbird/client/ServiceAccessor.java, tool/src/main/java/io/netbird/client/tool/VPNService.java, tool/src/main/java/io/netbird/client/tool/EngineRunner.java, tool/src/main/java/io/netbird/client/tool/AndroidPlatformFiles.java
Added debugBundle(boolean anonymize) method across service layers with exception handling; extended AndroidPlatformFiles constructor to accept and expose cacheDir path.
Troubleshoot Fragment & UI
app/src/main/java/io/netbird/client/ui/troubleshoot/TroubleshootFragment.java, app/src/main/res/layout/fragment_troubleshoot.xml, app/src/main/res/drawable/ic_menu_troubleshoot.xml
Added new TroubleshootFragment with trace-log and anonymize toggle switches; implements async debug-bundle generation with clipboard integration and toast feedback; includes layout and bug-report icon drawable.
Advanced Fragment Refactoring
app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java, app/src/main/res/layout/fragment_advanced.xml
Removed trace-log switch initialization, toggle listener, and share-logs button; deleted shareLog() method and related imports; updated layout constraints.
Navigation & Menu Configuration
app/src/main/res/menu/activity_main_drawer.xml, app/src/main/res/navigation/mobile_navigation.xml
Reorganized drawer menu items; added nav_troubleshoot menu entry and corresponding navigation fragment destination.
UI Resources
app/src/main/res/values/strings.xml
Added string resources: menu_troubleshoot, troubleshoot_debug_bundle, troubleshoot_anonymize.
Submodule Update
netbird
Updated netbird submodule commit reference.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Fragment as TroubleshootFragment
    participant Service as ServiceAccessor
    participant VPN as VPNService.MyLocalBinder
    participant Engine as EngineRunner
    participant GoClient as goClient (C/Go)
    participant Clipboard as ClipboardManager

    User->>Fragment: Clicks debug-bundle button
    Fragment->>Fragment: Disable button, start async task
    Fragment->>Service: debugBundle(anonymize)
    Service->>VPN: debugBundle(anonymize)
    VPN->>Engine: debugBundle(anonymize)
    Engine->>Engine: Fetch profile paths & cacheDir
    Engine->>Engine: Create AndroidPlatformFiles
    Engine->>GoClient: debugBundle(platformFiles, anonymize)
    GoClient-->>Engine: Return debug bundle (String)
    Engine-->>VPN: Return result
    VPN-->>Service: Return result
    Service-->>Fragment: Return result
    Fragment->>Fragment: Re-enable button
    Fragment->>Clipboard: Copy bundle to clipboard
    Fragment->>Fragment: Show success toast
    Note over User: Debug bundle copied & ready
    
    alt Exception occurs
        Service-->>Fragment: Throw Exception
        Fragment->>Fragment: Re-enable button
        Fragment->>Fragment: Log error
        Fragment->>Fragment: Show error toast (exception message)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • doromaraujo
  • lixmal

Poem

🐰 A troubleshoot tunnel opens wide,
Debug bundles bundled with pride!
Traces logged and anonymized,
The netbird's toolkit optimized! 🔧✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and concisely summarizes the main changes: adding debug bundle support for Android along with a new Troubleshoot UI.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/android-debug-bundle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
tool/src/main/java/io/netbird/client/tool/EngineRunner.java (1)

94-95: Extract AndroidPlatformFiles creation into one helper to avoid drift.

The same construction logic is duplicated in two places; a helper reduces future mismatch risk when path inputs evolve.

♻️ Small refactor
- var platformFiles = new AndroidPlatformFiles(configurationFilePath, stateFilePath, context.getCacheDir().getAbsolutePath());
+ var platformFiles = createPlatformFiles(configurationFilePath, stateFilePath);
...
- String cacheDir = context.getCacheDir().getAbsolutePath();
- var platformFiles = new AndroidPlatformFiles(configPath, statePath, cacheDir);
+ var platformFiles = createPlatformFiles(configPath, statePath);
+
+private AndroidPlatformFiles createPlatformFiles(String configPath, String statePath) {
+    return new AndroidPlatformFiles(configPath, statePath, context.getCacheDir().getAbsolutePath());
+}

Also applies to: 218-221

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tool/src/main/java/io/netbird/client/tool/EngineRunner.java` around lines 94
- 95, Extract the duplicated AndroidPlatformFiles construction into a single
helper method (e.g., createAndroidPlatformFiles) that accepts
configurationFilePath, stateFilePath and the cache directory path (use
context.getCacheDir().getAbsolutePath()) and returns a new AndroidPlatformFiles
instance; replace both direct instantiations (the one using "new
AndroidPlatformFiles(configurationFilePath, stateFilePath,
context.getCacheDir().getAbsolutePath())" and the other occurrence) with calls
to this helper to centralize construction and avoid future drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/io/netbird/client/MainActivity.java`:
- Around line 418-421: The code performs a check-then-use on the field mBinder
causing a potential race; capture a local snapshot (e.g., BinderType binder =
mBinder) before the null check and then use that local variable when calling
debugBundle(anonymize) in MainActivity to avoid mBinder changing between the
null check and the call; if the local snapshot is null, throw the same
Exception("VPN service not connected") so behavior is unchanged.

In
`@app/src/main/java/io/netbird/client/ui/troubleshoot/TroubleshootFragment.java`:
- Around line 69-87: The background thread callbacks access fragment fields
(binding/buttonDebugBundle) after async work which can be nullified in
onDestroyView; wrap all UI updates inside activity.runOnUiThread with a
null-check for binding (or capture a local reference to binding) and verify the
fragment is still added (isAdded()) before touching binding/buttonDebugBundle or
showing Toasts; apply these guards in both the success path (where debugBundle
key is copied to clipboard) and the exception path so no UI is accessed after
onDestroyView.

In `@app/src/main/res/layout/fragment_troubleshoot.xml`:
- Around line 76-80: The anonymize SwitchMaterial
(android:id="@+id/switch_anonymize") currently defaults to unchecked in
fragment_troubleshoot.xml causing debug bundles to include sensitive data;
update the Switch XML to set android:checked="true" so anonymization is enabled
by default, and also ensure any initialization code that later reads/writes this
control (the code that binds or reads switch_anonymize) respects and persists
this default state to preferences if applicable (i.e., add or verify a
preference key handling for switch_anonymize similar to the trace log switch).

In `@netbird`:
- Line 1: The netbird submodule currently points at a non-existent SHA
(a35ecf9aa8d8867df5f13a98840e152aa2cda0b4) so CI will fail; fix it by checking
out the netbird submodule remote, fetch the expected upstream branch, identify a
valid commit on that branch, update the netbird submodule pointer to that
reachable commit, stage the changed gitlink in the parent repo and commit/push
the update so .gitmodules/index reference now point to the valid commit on the
upstream branch.

In `@tool/src/main/java/io/netbird/client/tool/EngineRunner.java`:
- Around line 217-223: debugBundle currently calls goClient.debugBundle(...)
without the error-handling and synchronization used by other control methods;
wrap the call in a synchronized block on the same lock used elsewhere
(protecting goClient) and surround the call with try-catch(Exception e) that
logs the error and calls notifyError(e) to propagate failures to listeners,
keeping the creation of AndroidPlatformFiles and use of profileManager unchanged
and mirroring the pattern used in selectRoute/deselectRoute/renewTun.

---

Nitpick comments:
In `@tool/src/main/java/io/netbird/client/tool/EngineRunner.java`:
- Around line 94-95: Extract the duplicated AndroidPlatformFiles construction
into a single helper method (e.g., createAndroidPlatformFiles) that accepts
configurationFilePath, stateFilePath and the cache directory path (use
context.getCacheDir().getAbsolutePath()) and returns a new AndroidPlatformFiles
instance; replace both direct instantiations (the one using "new
AndroidPlatformFiles(configurationFilePath, stateFilePath,
context.getCacheDir().getAbsolutePath())" and the other occurrence) with calls
to this helper to centralize construction and avoid future drift.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 772db875-6634-4729-a33d-4e0a4cc71206

📥 Commits

Reviewing files that changed from the base of the PR and between 029585a and 401ec49.

📒 Files selected for processing (14)
  • app/src/main/java/io/netbird/client/MainActivity.java
  • app/src/main/java/io/netbird/client/ServiceAccessor.java
  • app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java
  • app/src/main/java/io/netbird/client/ui/troubleshoot/TroubleshootFragment.java
  • app/src/main/res/drawable/ic_menu_troubleshoot.xml
  • app/src/main/res/layout/fragment_advanced.xml
  • app/src/main/res/layout/fragment_troubleshoot.xml
  • app/src/main/res/menu/activity_main_drawer.xml
  • app/src/main/res/navigation/mobile_navigation.xml
  • app/src/main/res/values/strings.xml
  • netbird
  • tool/src/main/java/io/netbird/client/tool/AndroidPlatformFiles.java
  • tool/src/main/java/io/netbird/client/tool/EngineRunner.java
  • tool/src/main/java/io/netbird/client/tool/VPNService.java
💤 Files with no reviewable changes (1)
  • app/src/main/java/io/netbird/client/ui/advanced/AdvancedFragment.java

Comment on lines +418 to +421
if (mBinder == null) {
throw new Exception("VPN service not connected");
}
return mBinder.debugBundle(anonymize);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use a local binder snapshot to avoid a check-then-use race.

mBinder is read twice; it can change between the null-check and call, especially since this path is triggered off the UI thread. Capture once, then use that reference.

💡 Suggested fix
 `@Override`
 public String debugBundle(boolean anonymize) throws Exception {
-    if (mBinder == null) {
+    VPNService.MyLocalBinder binder = mBinder;
+    if (binder == null) {
         throw new Exception("VPN service not connected");
     }
-    return mBinder.debugBundle(anonymize);
+    return binder.debugBundle(anonymize);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (mBinder == null) {
throw new Exception("VPN service not connected");
}
return mBinder.debugBundle(anonymize);
VPNService.MyLocalBinder binder = mBinder;
if (binder == null) {
throw new Exception("VPN service not connected");
}
return binder.debugBundle(anonymize);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/io/netbird/client/MainActivity.java` around lines 418 -
421, The code performs a check-then-use on the field mBinder causing a potential
race; capture a local snapshot (e.g., BinderType binder = mBinder) before the
null check and then use that local variable when calling debugBundle(anonymize)
in MainActivity to avoid mBinder changing between the null check and the call;
if the local snapshot is null, throw the same Exception("VPN service not
connected") so behavior is unchanged.

Comment thread app/src/main/res/layout/fragment_troubleshoot.xml
Comment thread netbird Outdated
Comment thread tool/src/main/java/io/netbird/client/tool/EngineRunner.java
pappz added 3 commits April 14, 2026 19:39
Remove density-specific PNG icons and replace with vector drawables
using Material Design outlined style for consistent appearance.
Wrap goClient call with try-catch to log errors, matching the
pattern used by selectRoute and deselectRoute.
Check binding and isAdded() before accessing UI in background
thread callbacks to prevent NPE if the fragment is destroyed
while the debug bundle upload is in progress.
@pappz pappz merged commit e92798a into main Apr 20, 2026
7 checks passed
@pappz pappz deleted the fix/android-debug-bundle branch April 20, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants