diff --git a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java index e15b6c20..934f4ca0 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java +++ b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java @@ -92,6 +92,7 @@ public class CaptureService extends VpnService implements Runnable { private static final int NOTIFY_ID_VPNSERVICE = 1; private static CaptureService INSTANCE; private ParcelFileDescriptor mParcelFileDescriptor; + private boolean mIsAlwaysOnVPN; private CaptureSettings mSettings; private Billing mBilling; private Handler mHandler; @@ -171,32 +172,40 @@ public class CaptureService extends VpnService implements Runnable { } private int abortStart() { - // NOTE: startForeground must be called before stopSelf, otherwise an exception will occur + // NOTE: startForeground must be called before stopSelf, otherwise an exception will occur: + // android.app.ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground() setupNotifications(); + + // Note: in Android 12, this may generate a ForegroundServiceStartNotAllowedException + // if called when the app is in background. startForeground(NOTIFY_ID_VPNSERVICE, getStatusNotification()); stopSelf(); sendServiceStatus(SERVICE_STATUS_STOPPED); - return START_STICKY; + return START_NOT_STICKY; } @Override - public int onStartCommand(Intent intent, int flags, int startId) { + public int onStartCommand(@Nullable Intent intent, int flags, int startId) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); mHandler = new Handler(Looper.getMainLooper()); mBilling = Billing.newInstance(this); - if (intent == null) { - Log.d(CaptureService.TAG, "NULL intent onStartCommand"); - return abortStart(); - } - Log.d(CaptureService.TAG, "onStartCommand"); - mSettings = (CaptureSettings) intent.getSerializableExtra("settings"); - if (mSettings == null) { - Log.e(CaptureService.TAG, "Missing capture settings"); - return abortStart(); - } + // NOTE: a null intent may be delivered due to START_STICKY + mSettings = (CaptureSettings) ((intent == null) ? null : intent.getSerializableExtra("settings")); + if(mSettings == null) { + // An Intent without extras is delivered in case of always on VPN + // https://developer.android.com/guide/topics/connectivity/vpn#always-on + mIsAlwaysOnVPN = (intent != null); + + Log.d(CaptureService.TAG, "Missing capture settings, using previous ones"); + mSettings = new CaptureSettings(prefs); + if(mIsAlwaysOnVPN) + mSettings.root_capture = false; + } else + mIsAlwaysOnVPN = false; // Retrieve DNS server dns_server = FALLBACK_DNS_SERVER; @@ -284,7 +293,6 @@ public class CaptureService extends VpnService implements Runnable { } else app_filter_uid = -1; - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); mMalwareDetectionEnabled = Prefs.isMalwareDetectionEnabled(this, prefs); if(!mSettings.root_capture) { @@ -357,6 +365,8 @@ public class CaptureService extends VpnService implements Runnable { setupNotifications(); startForeground(NOTIFY_ID_VPNSERVICE, getStatusNotification()); + + // If the service is killed (e.g. due to low memory), then restart it with a NULL intent return START_STICKY; } @@ -621,6 +631,10 @@ public class CaptureService extends VpnService implements Runnable { (INSTANCE.mCaptureThread != null)); } + public static boolean isAlwaysOnVPN() { + return((INSTANCE != null) && INSTANCE.mIsAlwaysOnVPN); + } + private void checkBlacklistsUpdates() { if(!mMalwareDetectionEnabled || (mBlacklistsUpdateThread != null)) return; diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/StatusFragment.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/StatusFragment.java index 3dd7e12b..a142b576 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/fragments/StatusFragment.java +++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/StatusFragment.java @@ -338,6 +338,7 @@ private void refreshPcapDumpInfo() { ContextCompat.getDrawable(requireContext(), android.R.drawable.ic_media_play)); mMenuItemStartBtn.setTitle(R.string.start_button); mMenuItemStartBtn.setEnabled(true); + mMenuItemStartBtn.setVisible(true); mMenuSettings.setEnabled(true); } @@ -359,6 +360,7 @@ private void refreshPcapDumpInfo() { ContextCompat.getDrawable(requireContext(), R.drawable.ic_media_stop)); mMenuItemStartBtn.setTitle(R.string.stop_button); mMenuItemStartBtn.setEnabled(true); + mMenuItemStartBtn.setVisible(!CaptureService.isAlwaysOnVPN()); mMenuSettings.setEnabled(false); } diff --git a/app/src/main/java/com/emanuelef/remote_capture/model/CaptureSettings.java b/app/src/main/java/com/emanuelef/remote_capture/model/CaptureSettings.java index 30f20ddb..14a8b976 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/model/CaptureSettings.java +++ b/app/src/main/java/com/emanuelef/remote_capture/model/CaptureSettings.java @@ -7,19 +7,19 @@ import android.os.Bundle; import java.io.Serializable; public class CaptureSettings implements Serializable { - public final Prefs.DumpMode dump_mode; - public final String app_filter; - public final String collector_address; - public final int collector_port; - public final int http_server_port; - public final boolean socks5_enabled; - public final String socks5_proxy_address; - public final int socks5_proxy_port; - public final boolean ipv6_enabled; - public final boolean root_capture; - public final boolean pcapdroid_trailer; - public final String capture_interface; - public final String pcap_uri; + public Prefs.DumpMode dump_mode; + public String app_filter; + public String collector_address; + public int collector_port; + public int http_server_port; + public boolean socks5_enabled; + public String socks5_proxy_address; + public int socks5_proxy_port; + public boolean ipv6_enabled; + public boolean root_capture; + public boolean pcapdroid_trailer; + public String capture_interface; + public String pcap_uri; public CaptureSettings(SharedPreferences prefs) { dump_mode = Prefs.getDumpMode(prefs);