Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions app/src/main/java/com/openipc/pixelpilot/VideoActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -1294,12 +1294,30 @@ private void startVpnService() {
if (intent != null) {
startActivityForResult(intent, VPN_REQUEST_CODE);
} else {
Intent serviceIntent = new Intent(this, WfbNgVpnService.class);
startService(serviceIntent);
startServiceIfAllowed(new Intent(this, WfbNgVpnService.class));
}

}

/**
* Starts a service, or does nothing if the app is in the background.
*
* <p>Android 12 and later refuse a service start from a background app with
* BackgroundServiceStartNotAllowedException, and this activity can be created in the
* background: started while the screen is off, it threw from onCreate() and took the app
* down before it had shown anything. Nothing is lost by skipping it, because onResume() calls
* startVpnService() again once the app is actually in front.
*/
private void startServiceIfAllowed(Intent intent) {
try {
startService(intent);
} catch (IllegalStateException e) {
// BackgroundServiceStartNotAllowedException is an IllegalStateException. Catching the
// parent avoids naming a class that only exists from API 31, below minSdk.
Log.w(TAG, "not starting " + intent.getComponent() + " from the background", e);
}
}

private Uri openDvrFile() {
String dvrFolder = getSharedPreferences("general",
Context.MODE_PRIVATE).getString("dvr_folder_", "");
Expand Down Expand Up @@ -1452,8 +1470,7 @@ protected void onActivityResult(int requestCode, int resultCode, @Nullable Inten
} else if (requestCode == 100) { // VPN_REQUEST_CODE is 100
if (resultCode == RESULT_OK) {
// VPN permission granted, start the VPN service
Intent serviceIntent = new Intent(this, WfbNgVpnService.class);
startService(serviceIntent);
startServiceIfAllowed(new Intent(this, WfbNgVpnService.class));
} else {
// VPN permission not granted
Log.e(TAG, "VPN permission was not granted by the user.");
Expand Down Expand Up @@ -1547,11 +1564,10 @@ protected void onPause() {
videoPlayer.stopAudio();
wfbLinkManager.stopAdapters();

// Stop VPN service
// Stop VPN service - directly, not with a STOP_SERVICE start, which Android can refuse
// from the background. See WfbNgVpnService.stopRunning().
Log.w(TAG, "onPause: stopping service");
Intent intent = new Intent(this, WfbNgVpnService.class);
intent.setAction("STOP_SERVICE");
startService(intent);
WfbNgVpnService.stopRunning();
Comment thread
josephnef marked this conversation as resolved.
}

@Override
Expand Down
76 changes: 56 additions & 20 deletions app/src/main/java/com/openipc/pixelpilot/WfbNgVpnService.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,63 @@ public class WfbNgVpnService extends VpnService {
// Control flags
private volatile boolean isRunning = false;

// The instance that is running, if any - see stopRunning().
private static volatile WfbNgVpnService running;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && "STOP_SERVICE".equals(intent.getAction())) {
Log.i(TAG, "VPN Service stopping");
// Stop threads
isRunning = false;
public void onCreate() {
super.onCreate();
running = this;
}

if (udpToVpnThread != null) {
udpToVpnThread.interrupt();
}
if (vpnToUdpThread != null) {
vpnToUdpThread.interrupt();
}
/**
* Takes the tunnel down if it is up. Safe to call from anywhere in the app's process, in any
* state.
*
* <p>Not through startService(): Android refuses a service start from the background, and a
* stop sent that way is dropped along with it. onPause() normally still runs while the app
* may start services - going to sleep with the app in front, the stop went through - but
* nothing guarantees it, and an activity created in the background pauses there too. Not
* through stopService() either: once the interface is established the system holds its own
* binding to a VpnService, and stopService() leaves it running until the interface is closed.
* The service lives in this process, so closing it directly cannot be refused.
*/
public static void stopRunning() {
final WfbNgVpnService service = running;
if (service != null) {
service.shutDown();
}
}

// Close the interface
if (vpnInterface != null) {
try {
vpnInterface.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close VPN interface", e);
}
vpnInterface = null;
/** Stops the worker threads, closes the interface and stops the service. */
private void shutDown() {
Log.i(TAG, "VPN Service stopping");
// Stop threads
isRunning = false;

if (udpToVpnThread != null) {
udpToVpnThread.interrupt();
}
if (vpnToUdpThread != null) {
vpnToUdpThread.interrupt();
}

// Close the interface
if (vpnInterface != null) {
try {
vpnInterface.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close VPN interface", e);
}
stopSelf();
vpnInterface = null;
}
stopSelf();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && "STOP_SERVICE".equals(intent.getAction())) {
shutDown();
return START_NOT_STICKY;
}

Expand Down Expand Up @@ -211,6 +244,9 @@ public void run() {
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "VPN Service destroyed");
if (running == this) {
running = null;
}

// Stop threads
isRunning = false;
Expand Down
Loading