aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/src/main/java/com/wireguard/android/preference
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2020-03-09 19:06:11 +0530
committerHarsh Shandilya <me@msfjarvis.dev>2020-03-09 19:24:27 +0530
commit7d48bef70a56d4370856eedab619b1f83ac3d0d0 (patch)
tree76fd859578e499cd3a8fd2f402652530ea36a72d /ui/src/main/java/com/wireguard/android/preference
parentEnable nonnull generation for tunnel module (diff)
downloadwireguard-android-7d48bef70a56d4370856eedab619b1f83ac3d0d0.tar.xz
wireguard-android-7d48bef70a56d4370856eedab619b1f83ac3d0d0.zip
Rename app module to ui
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'ui/src/main/java/com/wireguard/android/preference')
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.java112
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.java92
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.java102
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/VersionPreference.java71
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java119
5 files changed, 496 insertions, 0 deletions
diff --git a/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.java b/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.java
new file mode 100644
index 00000000..565854b4
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/preference/LogExporterPreference.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.preference;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import androidx.annotation.Nullable;
+import com.google.android.material.snackbar.Snackbar;
+import androidx.preference.Preference;
+
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.wireguard.android.Application;
+import com.wireguard.android.R;
+import com.wireguard.android.util.DownloadsFileSaver;
+import com.wireguard.android.util.DownloadsFileSaver.DownloadsFile;
+import com.wireguard.android.util.ErrorMessages;
+import com.wireguard.android.util.FragmentUtils;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+/**
+ * Preference implementing a button that asynchronously exports logs.
+ */
+
+public class LogExporterPreference extends Preference {
+ private static final String TAG = "WireGuard/" + LogExporterPreference.class.getSimpleName();
+
+ @Nullable private String exportedFilePath;
+
+ public LogExporterPreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ private void exportLog() {
+ Application.getAsyncWorker().supplyAsync(() -> {
+ DownloadsFile outputFile = DownloadsFileSaver.save(getContext(), "wireguard-log.txt", "text/plain", true);
+ try {
+ final Process process = Runtime.getRuntime().exec(new String[]{
+ "logcat", "-b", "all", "-d", "-v", "threadtime", "*:V"});
+ try (final BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ final BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream())))
+ {
+ String line;
+ while ((line = stdout.readLine()) != null) {
+ outputFile.getOutputStream().write(line.getBytes());
+ outputFile.getOutputStream().write('\n');
+ }
+ outputFile.getOutputStream().close();
+ stdout.close();
+ if (process.waitFor() != 0) {
+ final StringBuilder errors = new StringBuilder();
+ errors.append(R.string.logcat_error);
+ while ((line = stderr.readLine()) != null)
+ errors.append(line);
+ throw new Exception(errors.toString());
+ }
+ }
+ } catch (final Exception e) {
+ outputFile.delete();
+ throw e;
+ }
+ return outputFile.getFileName();
+ }).whenComplete(this::exportLogComplete);
+ }
+
+ private void exportLogComplete(final String filePath, @Nullable final Throwable throwable) {
+ if (throwable != null) {
+ final String error = ErrorMessages.get(throwable);
+ final String message = getContext().getString(R.string.log_export_error, error);
+ Log.e(TAG, message, throwable);
+ Snackbar.make(
+ FragmentUtils.getPrefActivity(this).findViewById(android.R.id.content),
+ message, Snackbar.LENGTH_LONG).show();
+ setEnabled(true);
+ } else {
+ exportedFilePath = filePath;
+ notifyChanged();
+ }
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return exportedFilePath == null ?
+ getContext().getString(R.string.log_export_summary) :
+ getContext().getString(R.string.log_export_success, exportedFilePath);
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return getContext().getString(R.string.log_export_title);
+ }
+
+ @Override
+ protected void onClick() {
+ FragmentUtils.getPrefActivity(this).ensurePermissions(
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ (permissions, granted) -> {
+ if (granted.length > 0 && granted[0] == PackageManager.PERMISSION_GRANTED) {
+ setEnabled(false);
+ exportLog();
+ }
+ });
+ }
+
+}
diff --git a/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.java b/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.java
new file mode 100644
index 00000000..aac649dd
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright © 2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.preference;
+
+import android.content.Context;
+import android.content.Intent;
+import android.system.OsConstants;
+import android.util.AttributeSet;
+import android.widget.Toast;
+
+import com.wireguard.android.Application;
+import com.wireguard.android.R;
+import com.wireguard.android.util.ErrorMessages;
+import com.wireguard.android.util.ModuleLoader;
+import com.wireguard.android.util.ToolsInstaller;
+
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+
+public class ModuleDownloaderPreference extends Preference {
+ private State state = State.INITIAL;
+
+ public ModuleDownloaderPreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return getContext().getString(state.messageResourceId);
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return getContext().getString(R.string.module_installer_title);
+ }
+
+ @Override
+ protected void onClick() {
+ setState(State.WORKING);
+ Application.getAsyncWorker().supplyAsync(Application.getModuleLoader()::download).whenComplete(this::onDownloadResult);
+ }
+
+ private void onDownloadResult(final Integer result, @Nullable final Throwable throwable) {
+ if (throwable != null) {
+ setState(State.FAILURE);
+ Toast.makeText(getContext(), ErrorMessages.get(throwable), Toast.LENGTH_LONG).show();
+ } else if (result == OsConstants.ENOENT)
+ setState(State.NOTFOUND);
+ else if (result == OsConstants.EXIT_SUCCESS) {
+ setState(State.SUCCESS);
+ Application.getAsyncWorker().runAsync(() -> {
+ Thread.sleep(1000 * 5);
+ Intent i = getContext().getPackageManager().getLaunchIntentForPackage(getContext().getPackageName());
+ if (i == null)
+ return;
+ i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Application.get().startActivity(i);
+ System.exit(0);
+ });
+ } else
+ setState(State.FAILURE);
+ }
+
+ private void setState(final State state) {
+ if (this.state == state)
+ return;
+ this.state = state;
+ if (isEnabled() != state.shouldEnableView)
+ setEnabled(state.shouldEnableView);
+ notifyChanged();
+ }
+
+ private enum State {
+ INITIAL(R.string.module_installer_initial, true),
+ FAILURE(R.string.module_installer_error, true),
+ WORKING(R.string.module_installer_working, false),
+ SUCCESS(R.string.module_installer_success, false),
+ NOTFOUND(R.string.module_installer_not_found, false);
+
+ private final int messageResourceId;
+ private final boolean shouldEnableView;
+
+ State(final int messageResourceId, final boolean shouldEnableView) {
+ this.messageResourceId = messageResourceId;
+ this.shouldEnableView = shouldEnableView;
+ }
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.java b/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.java
new file mode 100644
index 00000000..78a7497b
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.preference;
+
+import android.content.Context;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import android.util.AttributeSet;
+
+import com.wireguard.android.Application;
+import com.wireguard.android.R;
+import com.wireguard.android.util.ToolsInstaller;
+
+/**
+ * Preference implementing a button that asynchronously runs {@code ToolsInstaller} and displays the
+ * result as the preference summary.
+ */
+
+public class ToolsInstallerPreference extends Preference {
+ private State state = State.INITIAL;
+
+ public ToolsInstallerPreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return getContext().getString(state.messageResourceId);
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return getContext().getString(R.string.tools_installer_title);
+ }
+
+ @Override
+ public void onAttached() {
+ super.onAttached();
+ Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::areInstalled).whenComplete(this::onCheckResult);
+ }
+
+ private void onCheckResult(final int state, @Nullable final Throwable throwable) {
+ if (throwable != null || state == ToolsInstaller.ERROR)
+ setState(State.INITIAL);
+ else if ((state & ToolsInstaller.YES) == ToolsInstaller.YES)
+ setState(State.ALREADY);
+ else if ((state & (ToolsInstaller.MAGISK | ToolsInstaller.NO)) == (ToolsInstaller.MAGISK | ToolsInstaller.NO))
+ setState(State.INITIAL_MAGISK);
+ else if ((state & (ToolsInstaller.SYSTEM | ToolsInstaller.NO)) == (ToolsInstaller.SYSTEM | ToolsInstaller.NO))
+ setState(State.INITIAL_SYSTEM);
+ else
+ setState(State.INITIAL);
+ }
+
+ @Override
+ protected void onClick() {
+ setState(State.WORKING);
+ Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::install).whenComplete(this::onInstallResult);
+ }
+
+ private void onInstallResult(final Integer result, @Nullable final Throwable throwable) {
+ if (throwable != null)
+ setState(State.FAILURE);
+ else if ((result & (ToolsInstaller.YES | ToolsInstaller.MAGISK)) == (ToolsInstaller.YES | ToolsInstaller.MAGISK))
+ setState(State.SUCCESS_MAGISK);
+ else if ((result & (ToolsInstaller.YES | ToolsInstaller.SYSTEM)) == (ToolsInstaller.YES | ToolsInstaller.SYSTEM))
+ setState(State.SUCCESS_SYSTEM);
+ else
+ setState(State.FAILURE);
+ }
+
+ private void setState(final State state) {
+ if (this.state == state)
+ return;
+ this.state = state;
+ if (isEnabled() != state.shouldEnableView)
+ setEnabled(state.shouldEnableView);
+ notifyChanged();
+ }
+
+ private enum State {
+ INITIAL(R.string.tools_installer_initial, true),
+ ALREADY(R.string.tools_installer_already, false),
+ FAILURE(R.string.tools_installer_failure, true),
+ WORKING(R.string.tools_installer_working, false),
+ INITIAL_SYSTEM(R.string.tools_installer_initial_system, true),
+ SUCCESS_SYSTEM(R.string.tools_installer_success_system, false),
+ INITIAL_MAGISK(R.string.tools_installer_initial_magisk, true),
+ SUCCESS_MAGISK(R.string.tools_installer_success_magisk, false);
+
+ private final int messageResourceId;
+ private final boolean shouldEnableView;
+
+ State(final int messageResourceId, final boolean shouldEnableView) {
+ this.messageResourceId = messageResourceId;
+ this.shouldEnableView = shouldEnableView;
+ }
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/preference/VersionPreference.java b/ui/src/main/java/com/wireguard/android/preference/VersionPreference.java
new file mode 100644
index 00000000..7e95a8ae
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/preference/VersionPreference.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.preference;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import android.util.AttributeSet;
+
+import com.wireguard.android.Application;
+import com.wireguard.android.BuildConfig;
+import com.wireguard.android.R;
+import com.wireguard.android.backend.Backend;
+import com.wireguard.android.backend.GoBackend;
+import com.wireguard.android.backend.WgQuickBackend;
+
+import java.util.Locale;
+
+public class VersionPreference extends Preference {
+ @Nullable private String versionSummary;
+
+ private String getBackendPrettyName(final Context context, final Backend backend) {
+ if (backend instanceof GoBackend)
+ return context.getString(R.string.type_name_kernel_module);
+ if (backend instanceof WgQuickBackend)
+ return context.getString(R.string.type_name_go_userspace);
+ return "";
+ }
+
+ public VersionPreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+
+ Application.getBackendAsync().thenAccept(backend -> {
+ versionSummary = getContext().getString(R.string.version_summary_checking, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH));
+ Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete((version, exception) -> {
+ versionSummary = exception == null
+ ? getContext().getString(R.string.version_summary, getBackendPrettyName(context, backend), version)
+ : getContext().getString(R.string.version_summary_unknown, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH));
+ notifyChanged();
+ });
+ });
+ }
+
+ @Nullable
+ @Override
+ public CharSequence getSummary() {
+ return versionSummary;
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return getContext().getString(R.string.version_title, BuildConfig.VERSION_NAME);
+ }
+
+ @Override
+ protected void onClick() {
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse("https://www.wireguard.com/"));
+ try {
+ getContext().startActivity(intent);
+ } catch (final ActivityNotFoundException ignored) {
+ }
+ }
+
+}
diff --git a/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java
new file mode 100644
index 00000000..3af412a5
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.preference;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import androidx.annotation.Nullable;
+import com.google.android.material.snackbar.Snackbar;
+import androidx.preference.Preference;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.wireguard.android.Application;
+import com.wireguard.android.R;
+import com.wireguard.android.model.ObservableTunnel;
+import com.wireguard.android.util.DownloadsFileSaver;
+import com.wireguard.android.util.DownloadsFileSaver.DownloadsFile;
+import com.wireguard.android.util.ErrorMessages;
+import com.wireguard.android.util.FragmentUtils;
+import com.wireguard.config.Config;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import java9.util.concurrent.CompletableFuture;
+
+/**
+ * Preference implementing a button that asynchronously exports config zips.
+ */
+
+public class ZipExporterPreference extends Preference {
+ private static final String TAG = "WireGuard/" + ZipExporterPreference.class.getSimpleName();
+
+ @Nullable private String exportedFilePath;
+
+ public ZipExporterPreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ private void exportZip() {
+ Application.getTunnelManager().getTunnels().thenAccept(this::exportZip);
+ }
+
+ private void exportZip(final List<ObservableTunnel> tunnels) {
+ final List<CompletableFuture<Config>> futureConfigs = new ArrayList<>(tunnels.size());
+ for (final ObservableTunnel tunnel : tunnels)
+ futureConfigs.add(tunnel.getConfigAsync().toCompletableFuture());
+ if (futureConfigs.isEmpty()) {
+ exportZipComplete(null, new IllegalArgumentException(
+ getContext().getString(R.string.no_tunnels_error)));
+ return;
+ }
+ CompletableFuture.allOf(futureConfigs.toArray(new CompletableFuture[futureConfigs.size()]))
+ .whenComplete((ignored1, exception) -> Application.getAsyncWorker().supplyAsync(() -> {
+ if (exception != null)
+ throw exception;
+ DownloadsFile outputFile = DownloadsFileSaver.save(getContext(), "wireguard-export.zip", "application/zip", true);
+ try (ZipOutputStream zip = new ZipOutputStream(outputFile.getOutputStream())) {
+ for (int i = 0; i < futureConfigs.size(); ++i) {
+ zip.putNextEntry(new ZipEntry(tunnels.get(i).getName() + ".conf"));
+ zip.write(futureConfigs.get(i).getNow(null).
+ toWgQuickString().getBytes(StandardCharsets.UTF_8));
+ }
+ zip.closeEntry();
+ } catch (final Exception e) {
+ outputFile.delete();
+ throw e;
+ }
+ return outputFile.getFileName();
+ }).whenComplete(this::exportZipComplete));
+ }
+
+ private void exportZipComplete(@Nullable final String filePath, @Nullable final Throwable throwable) {
+ if (throwable != null) {
+ final String error = ErrorMessages.get(throwable);
+ final String message = getContext().getString(R.string.zip_export_error, error);
+ Log.e(TAG, message, throwable);
+ Snackbar.make(
+ FragmentUtils.getPrefActivity(this).findViewById(android.R.id.content),
+ message, Snackbar.LENGTH_LONG).show();
+ setEnabled(true);
+ } else {
+ exportedFilePath = filePath;
+ notifyChanged();
+ }
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return exportedFilePath == null ?
+ getContext().getString(R.string.zip_export_summary) :
+ getContext().getString(R.string.zip_export_success, exportedFilePath);
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return getContext().getString(R.string.zip_export_title);
+ }
+
+ @Override
+ protected void onClick() {
+ FragmentUtils.getPrefActivity(this).ensurePermissions(
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ (permissions, granted) -> {
+ if (granted.length > 0 && granted[0] == PackageManager.PERMISSION_GRANTED) {
+ setEnabled(false);
+ exportZip();
+ }
+ });
+ }
+
+}