diff options
author | Samuel Holland <samuel@sholland.org> | 2018-01-01 02:06:37 -0600 |
---|---|---|
committer | Samuel Holland <samuel@sholland.org> | 2018-01-06 04:09:29 -0600 |
commit | 609194fae2332e6f2ccd7a4464bfa492ad661a6f (patch) | |
tree | 96a7cd9846a093dfcdacfef285b0a4d77000edf0 /app/src/main/java/com/wireguard/android/util | |
parent | Rename package widgets -> widget (diff) | |
download | wireguard-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')
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; + } +} |