Skip to content

Add Quick Settings Tile service for VPN control#136

Open
Dan-Kingsley wants to merge 4 commits intonetbirdio:mainfrom
Dan-Kingsley:main
Open

Add Quick Settings Tile service for VPN control#136
Dan-Kingsley wants to merge 4 commits intonetbirdio:mainfrom
Dan-Kingsley:main

Conversation

@Dan-Kingsley
Copy link

@Dan-Kingsley Dan-Kingsley commented Feb 5, 2026

I have been using NetBird for some time now, but I find it annoying to need to into the app to turn the VPN connection on. I have created a Icon and button to exist in the Quick Settings notification panel. so that this can be easily accessed without needing to go into the NetBird app

Summary by CodeRabbit

  • New Features
    • Added a Quick Settings tile for NetBird to toggle the VPN from device Quick Settings.
    • Tile shows current VPN status (active/inactive) and updates in real time.
    • Tapping the tile launches the app flow when VPN permissions are required and toggles/connects the VPN when possible.
  • Localization
    • Added tile label "NetBird" for display in Quick Settings.

@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

Adds a Quick Settings TileService, manifest entry, and string resource; TileService binds to the app VPNService, toggles or starts/stops the VPN based on binder state and permissions, and updates tile UI. Also exposes VPN running state via a new binder method.

Changes

Cohort / File(s) Summary
Manifest & Resources
app/src/main/AndroidManifest.xml, app/src/main/res/values/strings.xml
Adds exported NetbirdTileService declaration with android.service.quicksettings.action.QS_TILE, icon and label; adds quick_settings_tile_label string.
TileService Implementation
app/src/main/java/io/netbird/client/NetbirdTileService.java
New TileService subclass implementing onStartListening/onStopListening/onClick; binds/unbinds to VPNService, manages binding state and pending clicks, toggles or starts/stops VPN via binder, and updates tile ACTIVE/INACTIVE state.
VPN Binder API
tool/src/main/java/io/netbird/client/tool/VPNService.java
Adds public boolean isRunning() to VPNService.MyLocalBinder to expose engine running state to external binders.

Sequence Diagram

sequenceDiagram
    actor User
    participant QS as Quick Settings
    participant Tile as NetbirdTileService
    participant VPN as VPNService (binder)
    participant Engine as EngineRunner

    User->>QS: Tap Netbird tile
    QS->>Tile: onClick()
    alt Tile not bound
        Tile->>VPN: bindToVpnService()
        VPN-->>Tile: onServiceConnected (binder)
    end
    Tile->>VPN: isRunning()
    VPN->>Engine: engineRunner.isRunning()
    Engine-->>VPN: running? (true/false)
    alt running == true
        Tile->>VPN: request stop via binder
        VPN->>Engine: stop()
    else running == false
        Tile->>Tile: check BIND_QUICK_SETTINGS_TILE / VPN permission
        alt permission granted
            Tile->>VPN: startAndRunVpnService()
            VPN->>Engine: start()
        else permission missing
            Tile->>Tile: launch MainActivity for permission
        end
    end
    Tile->>Tile: updateTile() (ACTIVE/INACTIVE)
    Tile->>QS: reflect tile state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped a tile into the sky,

Tap me once and tunnels try,
I bind, I check, then start the run,
Toggle quick — connection's fun. 🌿

🚥 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 title 'Add Quick Settings Tile service for VPN control' directly and accurately describes the main change—introducing a Quick Settings tile for VPN control.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@Dan-Kingsley
Copy link
Author

Screenshot_20260205_152905_One UI Home.jpg

Copy link

@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: 1

🤖 Fix all issues with AI agents
In `@app/src/main/java/io/netbird/client/NetbirdTileService.java`:
- Around line 104-118: bindToVpnService currently starts the bind but you never
track a pending bind, so if onStopListening/unbindFromVpnService runs before
onServiceConnected the ServiceConnection remains; add a "bindingInProgress"
boolean set true in bindToVpnService, clear it in
serviceConnection.onServiceConnected and onServiceDisconnected, and modify
unbindFromVpnService to unbind when either isBound or bindingInProgress is true
(removing the serviceStateListener only if mBinder != null), then call
unbindService(serviceConnection) inside a try/catch to safely handle cases where
the system rejected the unbind, and reset isBound, bindingInProgress, and
mBinder appropriately.

Copy link

@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: 1

🤖 Fix all issues with AI agents
In `@app/src/main/java/io/netbird/client/NetbirdTileService.java`:
- Around line 72-77: Ensure pendingClick cannot persist across failed/aborted
binds: in NetbirdTileService.onStopListening() clear/reset the pendingClick flag
immediately (and any related state) so a subsequent successful bind won't act on
an old click; additionally, in the bind flow (the methods handling bindService
success/failure—e.g., your ServiceConnection callbacks and the code paths around
bindService / onServiceConnected / onServiceDisconnected used in the bind logic
between lines ~107–124) handle bind failures by clearing pendingClick and
aborting any pending toggle actions. Make sure both unbindFromVpnService() and
any bind-failure/error branches consistently reset pendingClick to a safe
default.

Copy link

@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: 2

🤖 Fix all issues with AI agents
In `@app/src/main/java/io/netbird/client/NetbirdTileService.java`:
- Around line 94-105: The toggle handler should avoid calling
startAndRunVpnService() when a bind is already in progress; update
handleToggle() to check the isBinding flag (or equivalent) before initiating the
service: if mBinder is null and isBinding is true, just set pendingClick = true
and return without calling startAndRunVpnService(), otherwise (when not binding)
set pendingClick and call startAndRunVpnService(); reference the handleToggle
method, mBinder, isBinding, pendingClick and startAndRunVpnService to locate and
implement this guard.
- Around line 84-88: NetbirdTileService currently calls
startActivityAndCollapse(Intent) which is deprecated and throws on Android 14+;
replace that call by building a PendingIntent for MainActivity (use the same
Intent with FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP), create the
PendingIntent with FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE, and pass that
PendingIntent to startActivityAndCollapse(PendingIntent) instead of the raw
Intent so it works on SDK 35+ and avoids UnsupportedOperationException.
🧹 Nitpick comments (1)
app/src/main/java/io/netbird/client/NetbirdTileService.java (1)

39-45: Consider removing the service state listener in onServiceDisconnected.

When the service disconnects unexpectedly (e.g., process killed), mBinder is set to null without removing the serviceStateListener. While the listener reference will become stale, explicitly removing it before nulling the binder ensures cleaner lifecycle management and mirrors the pattern in unbindFromVpnService().

♻️ Suggested improvement
         `@Override`
         public void onServiceDisconnected(ComponentName name) {
+            if (mBinder != null) {
+                mBinder.removeServiceStateListener(serviceStateListener);
+            }
             mBinder = null;
             isBound = false;
             isBinding = false;
             updateTile();
         }

@Dan-Kingsley
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

1 participant