summaryrefslogtreecommitdiff
path: root/app/src/main/java/se/leap/bitmaskclient/tor
diff options
context:
space:
mode:
authorcyBerta <cyberta@riseup.net>2021-11-11 22:16:15 +0100
committercyBerta <cyberta@riseup.net>2021-11-11 22:21:05 +0100
commita917cbe977f640345677b97dc9b00900d78c46b3 (patch)
tree0e2745401674ae0d83b52883474097e9ff851994 /app/src/main/java/se/leap/bitmaskclient/tor
parent0b687502d047253ca50b691c29336bc3e53a29d2 (diff)
use command pattern to start/stop tor service, similar to EIP and ProviderAPI service
Diffstat (limited to 'app/src/main/java/se/leap/bitmaskclient/tor')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java123
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/tor/TorServiceConnection.java73
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java11
3 files changed, 201 insertions, 6 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java b/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java
new file mode 100644
index 00000000..1946b861
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java
@@ -0,0 +1,123 @@
+package se.leap.bitmaskclient.tor;
+
+import android.app.Notification;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.annotation.WorkerThread;
+
+import org.torproject.jni.TorService;
+
+import java.util.concurrent.TimeoutException;
+
+import se.leap.bitmaskclient.base.utils.PreferenceHelper;
+
+import static se.leap.bitmaskclient.tor.TorNotificationManager.TOR_SERVICE_NOTIFICATION_ID;
+import static se.leap.bitmaskclient.tor.TorStatusObservable.waitUntil;
+
+public class TorServiceCommand {
+
+
+ private static String TAG = TorServiceCommand.class.getSimpleName();
+
+ // we bind the service before starting it as foreground service so that we avoid startForeground related RemoteExceptions
+ @WorkerThread
+ public static boolean startTorService(Context context, String action) throws InterruptedException {
+ Log.d(TAG, "startTorService");
+ try {
+ waitUntil(TorServiceCommand::isNotCancelled, 30);
+ } catch (TimeoutException e) {
+ e.printStackTrace();
+ }
+ TorServiceConnection torServiceConnection = initTorServiceConnection(context);
+ Log.d(TAG, "startTorService foreground: " + (torServiceConnection != null));
+ boolean startedForeground = false;
+ if (torServiceConnection == null) {
+ return startedForeground;
+ }
+
+ try {
+ Intent torServiceIntent = new Intent(context, TorService.class);
+ torServiceIntent.setAction(action);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ Notification notification = TorNotificationManager.buildTorForegroundNotification(context.getApplicationContext());
+ //noinspection NewApi
+ context.getApplicationContext().startForegroundService(torServiceIntent);
+ torServiceConnection.getService().startForeground(TOR_SERVICE_NOTIFICATION_ID, notification);
+ } else {
+ context.getApplicationContext().startService(torServiceIntent);
+ }
+ startedForeground = true;
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ }
+
+ if (torServiceConnection != null) {
+ torServiceConnection.close();
+ }
+
+ return startedForeground;
+ }
+
+ @WorkerThread
+ public static void stopTorService(Context context) {
+ if (TorStatusObservable.getStatus() == TorStatusObservable.TorStatus.OFF) {
+ return;
+ }
+ TorStatusObservable.markCancelled();
+
+ try {
+ Intent torServiceIntent = new Intent(context, TorService.class);
+ torServiceIntent.setAction(TorService.ACTION_STOP);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ //noinspection NewApi
+ context.getApplicationContext().startService(torServiceIntent);
+ } else {
+ context.getApplicationContext().startService(torServiceIntent);
+ }
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void stopTorServiceAsync(Context context) {
+ TorStatusObservable.markCancelled();
+ new Thread(() -> stopTorService(context)).start();
+ }
+
+ @WorkerThread
+ public static int getHttpTunnelPort(Context context) {
+ try {
+ TorServiceConnection torServiceConnection = initTorServiceConnection(context);
+ if (torServiceConnection != null) {
+ int tunnelPort = torServiceConnection.getService().getHttpTunnelPort();
+ torServiceConnection.close();
+ return tunnelPort;
+ }
+ } catch (InterruptedException | IllegalStateException e) {
+ e.printStackTrace();
+ }
+ return -1;
+ }
+
+ private static boolean isNotCancelled() {
+ return !TorStatusObservable.isCancelled();
+ }
+
+
+ private static TorServiceConnection initTorServiceConnection(Context context) throws InterruptedException, IllegalStateException {
+ Log.d(TAG, "initTorServiceConnection");
+ if (PreferenceHelper.getUseTor(context)) {
+ Log.d(TAG, "serviceConnection is still null");
+ if (!TorService.hasClientTransportPlugin()) {
+ TorService.setClientTransportPlugin(new ClientTransportPlugin(context.getApplicationContext()));
+ }
+ return new TorServiceConnection(context);
+ }
+ return null;
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceConnection.java b/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceConnection.java
new file mode 100644
index 00000000..0154983a
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceConnection.java
@@ -0,0 +1,73 @@
+package se.leap.bitmaskclient.tor;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.annotation.WorkerThread;
+
+import org.torproject.jni.TorService;
+
+import java.io.Closeable;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import se.leap.bitmaskclient.providersetup.ProviderAPI;
+
+import static se.leap.bitmaskclient.base.utils.ConfigHelper.ensureNotOnMainThread;
+
+public class TorServiceConnection implements Closeable {
+ private static final String TAG = TorServiceConnection.class.getSimpleName();
+ private final Context context;
+ private ServiceConnection serviceConnection;
+ private TorService torService;
+
+ @WorkerThread
+ public TorServiceConnection(Context context) throws InterruptedException, IllegalStateException {
+ this.context = context;
+ ensureNotOnMainThread(context);
+ initSynchronizedServiceConnection(context);
+ }
+
+ @Override
+ public void close() {
+ context.unbindService(serviceConnection);
+ }
+
+ private void initSynchronizedServiceConnection(final Context context) throws InterruptedException {
+ Log.d(TAG, "initSynchronizedServiceConnection");
+ final BlockingQueue<TorService> blockingQueue = new LinkedBlockingQueue<>(1);
+ this.serviceConnection = new ServiceConnection() {
+ volatile boolean mConnectedAtLeastOnce = false;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (!mConnectedAtLeastOnce) {
+ mConnectedAtLeastOnce = true;
+ Log.d(TAG, "onServiceConnected");
+ try {
+ TorService.LocalBinder binder = (TorService.LocalBinder) service;
+ blockingQueue.put(binder.getService());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ torService = null;
+ }
+ };
+ Intent intent = new Intent(context, TorService.class);
+ context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+ torService = blockingQueue.take();
+ }
+
+ public TorService getService() {
+ return torService;
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java b/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java
index ade22e6b..d426d51e 100644
--- a/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java
+++ b/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java
@@ -261,12 +261,11 @@ public class TorStatusObservable extends Observable {
return "";
}
- public static void shutdownTor(Context context) {
- getInstance().cancelled = true;
- getInstance().notifyObservers();
-
- Intent intent = new Intent(context, TorService.class);
- context.stopService(intent);
+ public static void markCancelled() {
+ if (!getInstance().cancelled) {
+ getInstance().cancelled = true;
+ getInstance().notifyObservers();
+ }
}
public static boolean isCancelled() {