aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/app/src/main/java/com/wireguard/android/util
diff options
context:
space:
mode:
authorSamuel Holland <samuel@sholland.org>2018-01-01 02:06:37 -0600
committerSamuel Holland <samuel@sholland.org>2018-01-06 04:09:29 -0600
commit609194fae2332e6f2ccd7a4464bfa492ad661a6f (patch)
tree96a7cd9846a093dfcdacfef285b0a4d77000edf0 /app/src/main/java/com/wireguard/android/util
parentRename package widgets -> widget (diff)
downloadwireguard-android-609194fae2332e6f2ccd7a4464bfa492ad661a6f.tar.xz
wireguard-android-609194fae2332e6f2ccd7a4464bfa492ad661a6f.zip
Serviceless rewrite, part 1
Signed-off-by: Samuel Holland <samuel@sholland.org>
Diffstat (limited to 'app/src/main/java/com/wireguard/android/util')
-rw-r--r--app/src/main/java/com/wireguard/android/util/AsyncWorker.java65
-rw-r--r--app/src/main/java/com/wireguard/android/util/ClipboardUtils.java32
-rw-r--r--app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java38
-rw-r--r--app/src/main/java/com/wireguard/android/util/RootShell.java109
4 files changed, 244 insertions, 0 deletions
diff --git a/app/src/main/java/com/wireguard/android/util/AsyncWorker.java b/app/src/main/java/com/wireguard/android/util/AsyncWorker.java
new file mode 100644
index 00000000..5f9f0a83
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/util/AsyncWorker.java
@@ -0,0 +1,65 @@
+package com.wireguard.android.util;
+
+import android.os.Handler;
+
+import com.wireguard.android.Application.ApplicationHandler;
+import com.wireguard.android.Application.ApplicationScope;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+import java9.util.concurrent.CompletableFuture;
+import java9.util.concurrent.CompletionStage;
+
+/**
+ * Helper class for running asynchronous tasks and ensuring they are completed on the main thread.
+ */
+
+@ApplicationScope
+public class AsyncWorker {
+ private final Executor executor;
+ private final Handler handler;
+
+ @Inject
+ public AsyncWorker(final Executor executor, @ApplicationHandler final Handler handler) {
+ this.executor = executor;
+ this.handler = handler;
+ }
+
+ public CompletionStage<Void> runAsync(final AsyncRunnable<?> runnable) {
+ final CompletableFuture<Void> future = new CompletableFuture<>();
+ executor.execute(() -> {
+ try {
+ runnable.run();
+ handler.post(() -> future.complete(null));
+ } catch (final Throwable t) {
+ handler.post(() -> future.completeExceptionally(t));
+ }
+ });
+ return future;
+ }
+
+ public <T> CompletionStage<T> supplyAsync(final AsyncSupplier<T, ?> supplier) {
+ final CompletableFuture<T> future = new CompletableFuture<>();
+ executor.execute(() -> {
+ try {
+ final T result = supplier.get();
+ handler.post(() -> future.complete(result));
+ } catch (final Throwable t) {
+ handler.post(() -> future.completeExceptionally(t));
+ }
+ });
+ return future;
+ }
+
+ @FunctionalInterface
+ public interface AsyncRunnable<E extends Throwable> {
+ void run() throws E;
+ }
+
+ @FunctionalInterface
+ public interface AsyncSupplier<T, E extends Throwable> {
+ T get() throws E;
+ }
+}
diff --git a/app/src/main/java/com/wireguard/android/util/ClipboardUtils.java b/app/src/main/java/com/wireguard/android/util/ClipboardUtils.java
new file mode 100644
index 00000000..20aeffff
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/util/ClipboardUtils.java
@@ -0,0 +1,32 @@
+package com.wireguard.android.util;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.view.View;
+import android.widget.TextView;
+
+import com.commonsware.cwac.crossport.design.widget.Snackbar;
+
+/**
+ * Created by samuel on 12/30/17.
+ */
+
+public final class ClipboardUtils {
+ private ClipboardUtils() {
+ }
+
+ public static void copyTextView(final View view) {
+ if (!(view instanceof TextView))
+ return;
+ final CharSequence text = ((TextView) view).getText();
+ if (text == null || text.length() == 0)
+ return;
+ final Object service = view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
+ if (!(service instanceof ClipboardManager))
+ return;
+ final CharSequence description = view.getContentDescription();
+ ((ClipboardManager) service).setPrimaryClip(ClipData.newPlainText(description, text));
+ Snackbar.make(view, description + " copied to clipboard", Snackbar.LENGTH_LONG).show();
+ }
+}
diff --git a/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java b/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java
new file mode 100644
index 00000000..a11529f4
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/util/ExceptionLoggers.java
@@ -0,0 +1,38 @@
+package com.wireguard.android.util;
+
+import android.util.Log;
+
+import java9.util.concurrent.CompletionException;
+import java9.util.function.BiConsumer;
+
+/**
+ * Helpers for logging exceptions from asynchronous tasks. These can be passed to
+ * {@code CompletionStage.handle()} at the end of an asynchronous future chain.
+ */
+
+public enum ExceptionLoggers implements BiConsumer<Object, Throwable> {
+ D(Log.DEBUG),
+ E(Log.ERROR);
+
+ private static final String TAG = ExceptionLoggers.class.getSimpleName();
+
+ private final int priority;
+
+ ExceptionLoggers(final int priority) {
+ this.priority = priority;
+ }
+
+ public static Throwable unwrap(final Throwable throwable) {
+ if (throwable instanceof CompletionException)
+ return throwable.getCause();
+ return throwable;
+ }
+
+ @Override
+ public void accept(final Object result, final Throwable throwable) {
+ if (throwable != null)
+ Log.println(Log.ERROR, TAG, Log.getStackTraceString(throwable));
+ else if (priority <= Log.DEBUG)
+ Log.println(priority, TAG, "Future completed successfully");
+ }
+}
diff --git a/app/src/main/java/com/wireguard/android/util/RootShell.java b/app/src/main/java/com/wireguard/android/util/RootShell.java
new file mode 100644
index 00000000..35f4735b
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/util/RootShell.java
@@ -0,0 +1,109 @@
+package com.wireguard.android.util;
+
+import android.content.Context;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.wireguard.android.Application.ApplicationContext;
+import com.wireguard.android.Application.ApplicationScope;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.inject.Inject;
+
+/**
+ * Helper class for running commands as root.
+ */
+
+@ApplicationScope
+public class RootShell {
+ private static final Pattern ERRNO_EXTRACTOR = Pattern.compile("error=(\\d+)");
+ /**
+ * Setup commands that are run at the beginning of each root shell. The trap command ensures
+ * access to the return value of the last command, since su itself always exits with 0.
+ */
+ private static final String TAG = "WireGuard/RootShell";
+ private static final String[][] libraryNamedExecutables = {
+ {"libwg.so", "wg"},
+ {"libwg-quick.so", "wg-quick"}
+ };
+
+ private final String preamble;
+
+ @Inject
+ public RootShell(@ApplicationContext final Context context) {
+ final String binDir = context.getCacheDir().getPath() + "/bin";
+ final String tmpDir = context.getCacheDir().getPath() + "/tmp";
+ final String libDir = context.getApplicationInfo().nativeLibraryDir;
+
+ new File(binDir).mkdirs();
+ new File(tmpDir).mkdirs();
+
+ StringBuilder builder = new StringBuilder();
+ for (final String[] libraryNamedExecutable : libraryNamedExecutables) {
+ final String arg1 = "'" + libDir + "/" + libraryNamedExecutable[0] + "'";
+ final String arg2 = "'" + binDir + "/" + libraryNamedExecutable[1] + "'";
+ builder.append(String.format("[ %s -ef %s ] || ln -sf %s %s || exit 31;", arg1, arg2, arg1, arg2));
+ }
+ builder.append(String.format("export PATH=\"%s:$PATH\" TMPDIR=\"%s\";", binDir, tmpDir));
+
+ preamble = builder.toString();
+ }
+
+ /**
+ * Run a command in a root shell.
+ *
+ * @param output Lines read from stdout are appended to this list. Pass null if the
+ * output from the shell is not important.
+ * @param command Command to run as root.
+ * @return The exit value of the last command run, or -1 if there was an internal error.
+ */
+ public int run(final List<String> output, final String command) {
+ int exitValue = -1;
+ try {
+ final ProcessBuilder builder = new ProcessBuilder();
+ builder.environment().put("LANG", "C");
+ builder.command("su", "-c", preamble + command);
+ final Process process = builder.start();
+ Log.d(TAG, "Running: " + command);
+ final InputStream stdout = process.getInputStream();
+ final InputStream stderr = process.getErrorStream();
+ final BufferedReader stdoutReader =
+ new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8));
+ final BufferedReader stderrReader =
+ new BufferedReader(new InputStreamReader(stderr, StandardCharsets.UTF_8));
+ String line;
+ while ((line = stdoutReader.readLine()) != null) {
+ if (output != null)
+ output.add(line);
+ Log.v(TAG, "stdout: " + line);
+ }
+ int linesOfStderr = 0;
+ String stderrLast = null;
+ while ((line = stderrReader.readLine()) != null) {
+ ++linesOfStderr;
+ stderrLast = line;
+ Log.v(TAG, "stderr: " + line);
+ }
+ exitValue = process.waitFor();
+ process.destroy();
+ if (exitValue == 1 && linesOfStderr == 1 && stderrLast.equals("Permission denied"))
+ exitValue = OsConstants.EACCES;
+ Log.d(TAG, "Exit status: " + exitValue);
+ } catch (IOException | InterruptedException e) {
+ Log.w(TAG, "Session failed with exception", e);
+ final Matcher match = ERRNO_EXTRACTOR.matcher(e.toString());
+ if (match.find())
+ exitValue = Integer.valueOf(match.group(1));
+ }
+ return exitValue;
+ }
+}