diff --git a/app/src/main/java/com/emanuelef/remote_capture/CaptureHelper.java b/app/src/main/java/com/emanuelef/remote_capture/CaptureHelper.java new file mode 100644 index 00000000..dbdfb9ec --- /dev/null +++ b/app/src/main/java/com/emanuelef/remote_capture/CaptureHelper.java @@ -0,0 +1,62 @@ +package com.emanuelef.remote_capture; + +import android.app.Activity; +import android.content.Intent; +import android.net.VpnService; + +import com.emanuelef.remote_capture.interfaces.CaptureStartListener; +import com.emanuelef.remote_capture.model.CaptureSettings; + +import androidx.activity.ComponentActivity; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.core.content.ContextCompat; + +public class CaptureHelper { + private final ComponentActivity mActivity; + private final ActivityResultLauncher mLauncher; + private CaptureSettings mSettings; + private CaptureStartListener mListener; + + public CaptureHelper(ComponentActivity activity) { + mActivity = activity; + mLauncher = activity.registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), this::captureServiceResult); + } + + private void captureServiceResult(final ActivityResult result) { + if(result.getResultCode() == Activity.RESULT_OK) + startCaptureOk(); + else if(mListener != null) + mListener.onCaptureStartResult(false); + } + + private void startCaptureOk() { + final Intent intent = new Intent(mActivity, CaptureService.class); + intent.putExtra("settings", mSettings); + + ContextCompat.startForegroundService(mActivity, intent); + if(mListener != null) + mListener.onCaptureStartResult(true); + } + + public void startCapture(CaptureSettings settings) { + mSettings = settings; + + if(settings.root_capture) { + startCaptureOk(); + return; + } + + Intent vpnPrepareIntent = VpnService.prepare(mActivity); + if(vpnPrepareIntent != null) + mLauncher.launch(vpnPrepareIntent); + else + startCaptureOk(); + } + + public void setListener(CaptureStartListener listener) { + mListener = listener; + } +} 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 8c458b2a..3838db82 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java +++ b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java @@ -192,10 +192,8 @@ public class CaptureService extends VpnService implements Runnable { if(mSettings.dump_mode == Prefs.DumpMode.HTTP_SERVER) mDumper = new HTTPServer(this, mSettings.http_server_port); else if(mSettings.dump_mode == Prefs.DumpMode.PCAP_FILE) { - String path = intent.getStringExtra(Prefs.PREF_PCAP_URI); - - if(path != null) { - mPcapUri = Uri.parse(path); + if(mSettings.pcap_uri != null) { + mPcapUri = Uri.parse(mSettings.pcap_uri); mDumper = new FileDumper(this, mPcapUri); } } else if(mSettings.dump_mode == Prefs.DumpMode.UDP_EXPORTER) { diff --git a/app/src/main/java/com/emanuelef/remote_capture/Utils.java b/app/src/main/java/com/emanuelef/remote_capture/Utils.java index 587f59c2..bcdab7fa 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/Utils.java +++ b/app/src/main/java/com/emanuelef/remote_capture/Utils.java @@ -79,7 +79,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/CaptureCtrl.java b/app/src/main/java/com/emanuelef/remote_capture/activities/CaptureCtrl.java index 4a40aa25..de886e6c 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/CaptureCtrl.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/CaptureCtrl.java @@ -1,6 +1,5 @@ package com.emanuelef.remote_capture.activities; -import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; @@ -12,16 +11,18 @@ import android.widget.Button; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; +import androidx.appcompat.app.AppCompatActivity; +import com.emanuelef.remote_capture.CaptureHelper; import com.emanuelef.remote_capture.CaptureService; import com.emanuelef.remote_capture.R; import com.emanuelef.remote_capture.Utils; import com.emanuelef.remote_capture.model.CaptureSettings; -public class CaptureCtrl extends Activity { +public class CaptureCtrl extends AppCompatActivity { private static final String TAG = "CaptureCtrl"; private static String calling_package = null; + private CaptureHelper mCapHelper; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -30,6 +31,12 @@ public class CaptureCtrl extends Activity { getWindow().addFlags(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); super.onCreate(savedInstanceState); + mCapHelper = new CaptureHelper(this); + mCapHelper.setListener(success -> { + setResult(success ? RESULT_OK : RESULT_CANCELED, null); + finish(); + }); + Intent intent = getIntent(); String action = intent.getStringExtra("action"); @@ -59,6 +66,12 @@ public class CaptureCtrl extends Activity { }, 1500); } + @Override + protected void onDestroy() { + mCapHelper = null; + super.onDestroy(); + } + private boolean isAlreadyAuthorized(@NonNull String action) { // Automatically authorize an app to stop the capture it started return !action.equals("start") && (calling_package != null) @@ -87,10 +100,9 @@ public class CaptureCtrl extends Activity { calling_package = getCallingPackage(); Log.d(TAG, "Starting capture, caller=" + calling_package); - final Intent intent = new Intent(this, CaptureService.class); - CaptureSettings settings = new CaptureSettings(req_intent); - intent.putExtra("settings", settings); - ContextCompat.startForegroundService(this, intent); + // will call the mCapHelper listener + mCapHelper.startCapture(new CaptureSettings(req_intent)); + return; } else if(action.equals("stop")) { Log.d(TAG, "Stopping capture"); diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java b/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java index e7d1eecd..9bc0d3ed 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/MainActivity.java @@ -29,7 +29,6 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; -import android.net.VpnService; import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultLauncher; @@ -39,7 +38,6 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; -import androidx.core.content.ContextCompat; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; @@ -61,6 +59,7 @@ import android.view.MenuItem; import android.view.View; import android.widget.TextView; +import com.emanuelef.remote_capture.CaptureHelper; import com.emanuelef.remote_capture.fragments.ConnectionsFragment; import com.emanuelef.remote_capture.fragments.StatusFragment; import com.emanuelef.remote_capture.interfaces.AppStateListener; @@ -89,6 +88,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig private DrawerLayout mDrawer; private SharedPreferences mPrefs; private NavigationView mNavView; + private CaptureHelper mCapHelper; private boolean usingMediaStore; private static final String TAG = "Main"; @@ -102,8 +102,6 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig public static final String GITHUB_DOCS_URL = "https://emanuele-f.github.io/PCAPdroid"; public static final String DONATE_URL = "https://emanuele-f.github.io/PCAPdroid/donate"; - private final ActivityResultLauncher captureServiceLauncher = - registerForActivityResult(new StartActivityForResult(), this::captureServiceResult); private final ActivityResultLauncher pcapFileLauncher = registerForActivityResult(new StartActivityForResult(), this::pcapFileResult); private final ActivityResultLauncher requestPermissionLauncher = @@ -122,6 +120,13 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig mPrefs = PreferenceManager.getDefaultSharedPreferences(this); mPcapUri = CaptureService.getPcapUri(); + mCapHelper = new CaptureHelper(this); + mCapHelper.setListener(success -> { + if(!success) { + Log.w(TAG, "VPN request failed"); + appStateReady(); + } + }); CaocConfig.Builder.create() .errorDrawable(R.drawable.ic_app_crash) @@ -171,6 +176,8 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig if(mReceiver != null) LocalBroadcastManager.getInstance(this) .unregisterReceiver(mReceiver); + + mCapHelper = null; } @Override @@ -448,29 +455,6 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig return super.onOptionsItemSelected(item); } - private void captureServiceResult(final ActivityResult result) { - if(result.getResultCode() == RESULT_OK) { - captureServiceOk(); - } else { - Log.w(TAG, "VPN request failed"); - appStateReady(); - } - } - - private void captureServiceOk() { - final Intent intent = new Intent(MainActivity.this, CaptureService.class); - - CaptureSettings settings = new CaptureSettings(mPrefs); - intent.putExtra("settings", settings); - - if((mPcapUri != null) && (Prefs.getDumpMode(mPrefs) == Prefs.DumpMode.PCAP_FILE)) - intent.putExtra(Prefs.PREF_PCAP_URI, mPcapUri.toString()); - - Log.d(TAG, "onActivityResult -> start CaptureService"); - - ContextCompat.startForegroundService(this, intent); - } - private void pcapFileResult(final ActivityResult result) { if (result.getResultCode() == RESULT_OK && result.getData() != null) { startWithPcapFile(result.getData().getData()); @@ -499,17 +483,8 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig private void startCaptureService() { appStateStarting(); - if(Prefs.isRootCaptureEnabled(mPrefs)) { - captureServiceOk(); - return; - } - - Intent vpnPrepareIntent = VpnService.prepare(MainActivity.this); - - if (vpnPrepareIntent != null) - captureServiceLauncher.launch(vpnPrepareIntent); - else - captureServiceOk(); + String pcap_uri = ((mPcapUri != null) && (Prefs.getDumpMode(mPrefs) == Prefs.DumpMode.PCAP_FILE)) ? mPcapUri.toString() : ""; + mCapHelper.startCapture(new CaptureSettings(mPrefs, pcap_uri)); } public void toggleService() { diff --git a/app/src/main/java/com/emanuelef/remote_capture/interfaces/CaptureStartListener.java b/app/src/main/java/com/emanuelef/remote_capture/interfaces/CaptureStartListener.java new file mode 100644 index 00000000..48c9364f --- /dev/null +++ b/app/src/main/java/com/emanuelef/remote_capture/interfaces/CaptureStartListener.java @@ -0,0 +1,24 @@ +/* + * This file is part of PCAPdroid. + * + * PCAPdroid is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PCAPdroid is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PCAPdroid. If not, see . + * + * Copyright 2020-21 - Emanuele Faranda + */ + +package com.emanuelef.remote_capture.interfaces; + +public interface CaptureStartListener { + void onCaptureStartResult(boolean success); +} 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 37276d11..5a1431cd 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 @@ -19,8 +19,9 @@ public class CaptureSettings implements Serializable { public final boolean root_capture; public final boolean pcapdroid_trailer; public final String capture_interface; + public final String pcap_uri; - public CaptureSettings(SharedPreferences prefs) { + public CaptureSettings(SharedPreferences prefs, String _pcap_uri) { dump_mode = Prefs.getDumpMode(prefs); app_filter = Prefs.getAppFilter(prefs); collector_address = Prefs.getCollectorIp(prefs); @@ -33,6 +34,7 @@ public class CaptureSettings implements Serializable { root_capture = Prefs.isRootCaptureEnabled(prefs); pcapdroid_trailer = Prefs.isPcapdroidTrailerEnabled(prefs); capture_interface = Prefs.getCaptureInterface(prefs); + pcap_uri = _pcap_uri; } public CaptureSettings(Intent intent) { @@ -48,6 +50,7 @@ public class CaptureSettings implements Serializable { root_capture = intent.getBooleanExtra(Prefs.PREF_ROOT_CAPTURE, false); pcapdroid_trailer = intent.getBooleanExtra(Prefs.PREF_PCAPDROID_TRAILER, false); capture_interface = getString(intent, Prefs.PREF_CAPTURE_INTERFACE, "@inet"); + pcap_uri = getString(intent, Prefs.PREF_PCAP_URI, ""); } private static String getString(Intent intent, String key, String def_value) { diff --git a/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java b/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java index e82b8eea..fd58776d 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java +++ b/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java @@ -36,7 +36,7 @@ public class Prefs { public static final String PREF_APP_FILTER = "app_filter"; public static final String PREF_HTTP_SERVER_PORT = "http_server_port"; public static final String PREF_PCAP_DUMP_MODE = "pcap_dump_mode"; - public static final String PREF_PCAP_URI = "pcap_path"; + public static final String PREF_PCAP_URI = "pcap_uri"; public static final String DEFAULT_DUMP_MODE = DUMP_HTTP_SERVER; public static final String PREF_IPV6_ENABLED = "ipv6_enabled"; public static final String PREF_APP_LANGUAGE = "app_language"; diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 5dd6b4eb..37cfd3a5 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -169,6 +169,6 @@ Interfaccia di Cattura Internet Tutte le Interfacce - Cattura in esecuzione su \"%1$s\" + Cattura in corso da \"%1$s\" diff --git a/docs/app_api.md b/docs/app_api.md index deab10c2..09aaa6ca 100644 --- a/docs/app_api.md +++ b/docs/app_api.md @@ -75,6 +75,7 @@ As shown above, the capture settings can be specified by using intent extras. Th | collector_address | string | the IP address of the collector in udp_exporter mode | | collector_port | int | the UDP port of the collector in udp_exporter mode | | http_server_port | int | the HTTP server port in http_server mode | +| pcap_uri | string | the URI for the PCAP dump in pcap_file mode | | socks5_enabled | bool | true to enable the SOCKS5 proxy | | socks5_proxy_address | string | the IP address of the SOCKS5 proxy | | socks5_proxy_port | int | the TCP port of the SOCKS5 proxy |