Skip to content

Do not crash when the activity is created in the background - #126

Merged
josephnef merged 2 commits into
OpenIPC:masterfrom
iflyhere:fix/background-vpn-start
Oct 18, 2026
Merged

josephnef merged 2 commits into
OpenIPC:masterfrom
iflyhere:fix/background-vpn-start

Conversation

@iflyhere

@iflyhere iflyhere commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

startVpnService() calls startService() unguarded, from onCreate() as well as onResume(),
and Android 12 and later refuse a service start from a background app with
BackgroundServiceStartNotAllowedException. VideoActivity can be created in the background —
that is how its USB filter launches it when the adapter is plugged in with the screen off — and
then the exception escapes onCreate() and the app is gone before it has shown anything.

Reproduced on a Quest 3 by starting the activity while the headset was asleep:

FATAL EXCEPTION: main
Unable to start activity ComponentInfo{com.openipc.pixelpilot/com.openipc.pixelpilot.VideoActivity}:
android.app.BackgroundServiceStartNotAllowedException: Not allowed to start service
Intent { cmp=com.openipc.pixelpilot/.WfbNgVpnService }: app is in background
    at com.openipc.pixelpilot.VideoActivity.startVpnService(VideoActivity.java:1298)
    at com.openipc.pixelpilot.VideoActivity.onCreate(VideoActivity.java:340)

The start is skipped with a warning instead. Nothing is lost by that: onResume() calls
startVpnService() again once the app is actually in front, which is when the tunnel is wanted.
The start after the VPN consent goes through the same guard.

The stop in onPause() no longer goes through a service start at all. Routed through the guard,
a refused start would have dropped the stop with it and left the tunnel up; stopService() would
not help either, because once the interface is established the system holds its own binding to
a VpnService. The service runs in the app's own process, so onPause() shuts the running
instance down directly through WfbNgVpnService.stopRunning().

It catches IllegalStateException, which BackgroundServiceStartNotAllowedException extends,
rather than naming a class that only exists from API 31 — minSdk is 26.

Testing

On a Quest 3, the same launch with the headset asleep now logs

not starting ComponentInfo{com.openipc.pixelpilot/com.openipc.pixelpilot.WfbNgVpnService} from the background

and the app survives. When the headset was put on and the app came to the front, onResume()
started the tunnel: tun0 came up at 10.5.0.3/24. Going to sleep with the app in front takes the
tunnel down (VPN Service stopping, destroyed), and waking brings it back. The process stayed
the same throughout, with nothing in the crash buffer.

startVpnService() calls startService() unguarded, from onCreate() as well as onResume(), and
Android 12 and later refuse a service start from a background app with
BackgroundServiceStartNotAllowedException. VideoActivity can be created in the background - that
is how its USB filter launches it when the adapter is plugged in with the screen off - and then
the exception escapes onCreate() and the app is gone before it has shown anything.

Reproduced on a Quest 3 by starting the activity while the headset was asleep:

  FATAL EXCEPTION: main
  Unable to start activity ... VideoActivity:
  android.app.BackgroundServiceStartNotAllowedException: Not allowed to start service
  Intent { cmp=com.openipc.pixelpilot/.WfbNgVpnService }: app is in background
      at VideoActivity.startVpnService(VideoActivity.java:1298)
      at VideoActivity.onCreate(VideoActivity.java:340)

The start is skipped with a warning instead. Nothing is lost by that: onResume() calls
startVpnService() again once the app is actually in front, which is when the tunnel is wanted.
The stop in onPause() and the start after the VPN consent go through the same guard, since both
can run with the app on its way to the background.

It catches IllegalStateException, which BackgroundServiceStartNotAllowedException extends,
rather than naming a class that only exists from API 31 - minSdk is lower than that.
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

PR Summary by Qodo

Prevent background VPN starts from crashing VideoActivity

🐞 Bug fix 🕐 20-40 Minutes

Grey Divider

AI Description

• Guard VPN service starts so background activity launches survive Android 12 service restrictions.
• Retry the start when the activity resumes or VPN consent is granted.
• Shut down the running tunnel directly on pause, without a potentially refused service start.
Diagram

graph TD
  A["Activity launch"] --> B{"VPN consent?"} -->|Prepared| D["Guarded start"] -->|Allowed| F["VPN service"] -->|Pause: direct shutdown| G["Tunnel closed"]
  B -->|Required| C["Consent request"] -->|Granted| D
  D -->|Background denial| E["Skip start"] -->|Resume: retry| D
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Start only on resume
  • ➕ Avoids attempting a VPN start during background onCreate.
  • ➖ Resume and VPN consent callbacks can still encounter a refused start; pause still needs a shutdown path independent of startService.

Recommendation: Keep the guarded starts and direct shutdown: they cover every current start path and prevent a refused start from also preventing tunnel teardown. Starting only on resume would reduce attempts but would not fully address the lifecycle restrictions.

Files changed (2) +80 / -28

Bug fix (2) +80 / -28
VideoActivity.javaGuard VPN starts and stop the running service on pause +24/-8

Guard VPN starts and stop the running service on pause

• Routes VPN starts from initialization and the consent result through a helper that logs and absorbs IllegalStateException, preventing a refused background start from crashing the activity. Replaces the STOP_SERVICE start in onPause with a direct call to WfbNgVpnService.stopRunning().

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java

WfbNgVpnService.javaExpose direct shutdown for the running VPN instance +56/-20

Expose direct shutdown for the running VPN instance

• Tracks the current service instance and adds stopRunning() to interrupt workers, close the VPN interface, and stop the service without issuing a new start command. The existing STOP_SERVICE action reuses that shutdown routine, and onDestroy clears the tracked instance.

app/src/main/java/com/openipc/pixelpilot/WfbNgVpnService.java

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Leaving the app can leave the VPN active ✓ Resolved
Description
onPause() sends STOP_SERVICE through startServiceIfAllowed(), which logs and discards the
command when startService() throws IllegalStateException. If Android rejects that call as the
app enters the background, WfbNgVpnService never receives the command that closes its VPN
interface and stops the service.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1571]

+        startServiceIfAllowed(intent);
Evidence
onPause() now routes its stop intent through the helper, which returns without delivering it when
a background start is rejected. The service handles STOP_SERVICE by closing the interface and
calling stopSelf(); without that command or destruction, it remains running.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1311-1318]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1556-1571]
app/src/main/java/com/openipc/pixelpilot/WfbNgVpnService.java[37-75]
app/src/main/java/com/openipc/pixelpilot/WfbNgVpnService.java[209-233]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The background-start guard can discard the `STOP_SERVICE` command from `onPause()`, leaving the VPN active.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1566-1572]
- app/src/main/java/com/openipc/pixelpilot/WfbNgVpnService.java[209-233]
## Recommended Fix
Use `stopService()` for pause-time teardown instead of starting the service with a stop action. Preserve the VPN interface and worker-thread cleanup in the service's `onDestroy()` path.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. A delayed start leaves the VPN active 🐞 Bug ☼ Reliability
Description
stopRunning() does nothing while running is null, even if startService() has already requested
service creation. If the activity pauses before the service's onCreate() runs, the earlier request
can still reach onStartCommand() and establish the tunnel after the activity has paused.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1570]

+        WfbNgVpnService.stopRunning();
Evidence
The activity requests a normal service start from onCreate() and onResume(). The new pause path
only stops an instance already published by the service's onCreate(); a later normal
onStartCommand() proceeds to establish the interface without checking whether the activity has
since paused.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1290-1298]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1564-1570]
app/src/main/java/com/openipc/pixelpilot/WfbNgVpnService.java[35-42]
app/src/main/java/com/openipc/pixelpilot/WfbNgVpnService.java[56-60]
app/src/main/java/com/openipc/pixelpilot/WfbNgVpnService.java[89-105]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A pause can occur after a VPN start is requested but before the service instance exists. The direct stop then does nothing, and the pending start establishes the VPN after the activity has paused.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1567-1570]
- app/src/main/java/com/openipc/pixelpilot/WfbNgVpnService.java[56-60]
- app/src/main/java/com/openipc/pixelpilot/WfbNgVpnService.java[89-105]
## Recommended Fix
Coordinate start and stop requests so a pause also cancels or invalidates an outstanding start. Before establishing the interface, reject a start invalidated by a later pause; preserve the ability to start on a subsequent resume.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can move or hide resolved findings to focus on what still needs action

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread app/src/main/java/com/openipc/pixelpilot/VideoActivity.java Outdated
From review: onPause() sent STOP_SERVICE through startServiceIfAllowed(), so whenever Android
refused that start the stop was dropped with it and the tunnel stayed up.

How often that happens is narrower than it might look. Going to sleep with the app in front,
the stop went through - onPause() still ran while the app was allowed to start services, and
the service logged its own shutdown. The crash this branch fixes came from onCreate() in an
activity started while already asleep, not from onPause() on the way into sleep. But nothing
guarantees onPause() runs before the app counts as background, and a stop routed through a call
that may be refused silently is the wrong shape whether or not it bites today.

stopService() would not have been the fix. Once the interface is established the system holds
its own binding to a VpnService, so stopService() leaves the service - and the tunnel - running
until the interface is closed, and onDestroy() never comes.

The service runs in the app's own process, so onPause() now reaches the running instance and
shuts it down directly: worker threads stopped, interface closed, stopSelf(). That cannot be
refused. The STOP_SERVICE action does the same through the same method.
@josephnef josephnef closed this Oct 18, 2026
@josephnef josephnef reopened this Oct 18, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 7a0bf4b

@josephnef josephnef left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving. The analysis in the description is right: a BackgroundServiceStartNotAllowedException out of onCreate when the USB filter launches the activity with the screen off, and a stop that went through startService was subject to the same refusal. Routing the stop through the in-process instance is the correct fix given the system's own binding to an established VpnService, and running is cleared in onDestroy, so it does not leak. Catching IllegalStateException for minSdk 26 is the right call. CI is green on the reopened run.

Heads-up for sequencing: this conflicts with #129 in VideoActivity.java (same onPause region). Since this is the smaller and more urgent fix it should land first and #129 rebase on it.

@josephnef
josephnef merged commit 62dca00 into OpenIPC:master Oct 18, 2026
4 checks passed
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