Serviceless rewrite, part 1
Signed-off-by: Samuel Holland <samuel@sholland.org>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user