summaryrefslogtreecommitdiffhomepage
path: root/app
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2018-04-29 02:04:28 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2018-04-29 07:53:56 +0200
commit622f41f11f92005e2dd3791fd13b0ace294958d5 (patch)
tree7e811b6b9940881c36be757dbe3f4bd3223e0535 /app
parentf4e462fabd366873cf0f1d6927469d6bac3bec0d (diff)
Allow exporting to zip file
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'app')
-rw-r--r--app/src/main/AndroidManifest.xml2
-rw-r--r--app/src/main/java/com/wireguard/android/activity/SettingsActivity.java50
-rw-r--r--app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java123
-rw-r--r--app/src/main/res/values/strings.xml6
-rw-r--r--app/src/main/res/xml/preferences.xml1
5 files changed, 180 insertions, 2 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8cd0f623..63d8aa78 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,7 +5,7 @@
android:installLocation="internalOnly">
<uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
diff --git a/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java b/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java
index e3cd46b1..41761b32 100644
--- a/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java
+++ b/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java
@@ -1,6 +1,7 @@
package com.wireguard.android.activity;
import android.app.Activity;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
@@ -9,11 +10,60 @@ import com.wireguard.android.Application;
import com.wireguard.android.R;
import com.wireguard.android.backend.WgQuickBackend;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
/**
* Interface for changing application-global persistent settings.
*/
public class SettingsActivity extends Activity {
+ @FunctionalInterface
+ public interface PermissionRequestCallback {
+ void done(String[] permissions, int[] grantResults);
+ }
+
+ private HashMap<Integer, PermissionRequestCallback> permissionRequestCallbacks = new HashMap<>();
+ private int permissionRequestCounter = 0;
+
+ public synchronized void ensurePermissions(String[] permissions, PermissionRequestCallback cb) {
+ /* TODO(MSF): since when porting to AppCompat, you'll be replacing checkSelfPermission
+ * and requestPermission with AppCompat.checkSelfPermission and AppCompat.requestPermission,
+ * you can remove this SDK_INT block entirely here, and count on the compat lib to do
+ * the right thing. */
+ if (android.os.Build.VERSION.SDK_INT < 23) {
+ int[] granted = new int[permissions.length];
+ Arrays.fill(granted, PackageManager.PERMISSION_GRANTED);
+ cb.done(permissions, granted);
+ } else {
+ List<String> needPermissions = new ArrayList<>(permissions.length);
+ for (final String permission : permissions) {
+ if (getApplicationContext().checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED)
+ needPermissions.add(permission);
+ }
+ if (needPermissions.isEmpty()) {
+ int[] granted = new int[permissions.length];
+ Arrays.fill(granted, PackageManager.PERMISSION_GRANTED);
+ cb.done(permissions, granted);
+ return;
+ }
+ int idx = permissionRequestCounter++;
+ permissionRequestCallbacks.put(idx, cb);
+ requestPermissions(needPermissions.toArray(new String[needPermissions.size()]), idx);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ final PermissionRequestCallback f = permissionRequestCallbacks.get(requestCode);
+ if (f != null) {
+ permissionRequestCallbacks.remove(requestCode);
+ f.done(permissions, grantResults);
+ }
+ }
+
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
diff --git a/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java b/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java
new file mode 100644
index 00000000..2101420f
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java
@@ -0,0 +1,123 @@
+package com.wireguard.android.preference;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Environment;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.commonsware.cwac.crossport.design.widget.Snackbar;
+import com.wireguard.android.Application;
+import com.wireguard.android.Application.ApplicationComponent;
+import com.wireguard.android.R;
+import com.wireguard.android.activity.SettingsActivity;
+import com.wireguard.android.model.Tunnel;
+import com.wireguard.android.model.TunnelManager;
+import com.wireguard.android.util.AsyncWorker;
+import com.wireguard.android.util.ExceptionLoggers;
+import com.wireguard.config.Config;
+
+import java.io.File;
+import java.io.FileOutputStream;
+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();
+
+ private final AsyncWorker asyncWorker;
+ private final TunnelManager tunnelManager;
+ private String exportedFilePath = null;
+
+ @SuppressWarnings({"SameParameterValue", "WeakerAccess"})
+ public ZipExporterPreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ final ApplicationComponent applicationComponent = Application.getComponent();
+ asyncWorker = applicationComponent.getAsyncWorker();
+ tunnelManager = applicationComponent.getTunnelManager();
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ if (exportedFilePath == null)
+ return getContext().getString(R.string.export_summary);
+ else
+ return getContext().getString(R.string.export_success, exportedFilePath);
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return getContext().getString(getTitleRes());
+ }
+
+ @Override
+ public int getTitleRes() {
+ return R.string.zip_exporter_title;
+ }
+
+ private void exportZip() {
+ List<Tunnel> tunnels = new ArrayList<>(tunnelManager.getTunnels());
+ List<CompletableFuture<Config>> futureConfigs = new ArrayList<>(tunnels.size());
+ for (final Tunnel tunnel : tunnels)
+ futureConfigs.add(tunnel.getConfigAsync().toCompletableFuture());
+ if (futureConfigs.isEmpty()) {
+ exportZipComplete(null, new IllegalArgumentException("No tunnels exist"));
+ return;
+ }
+ CompletableFuture.allOf(futureConfigs.toArray(new CompletableFuture[futureConfigs.size()]))
+ .whenComplete((ignored1, exception) -> {
+ asyncWorker.supplyAsync(() -> {
+ if (exception != null)
+ throw exception;
+ final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+ final File file = new File(path, "wireguard-export.zip");
+ try {
+ path.mkdirs();
+ final ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(file));
+ for (int i = 0; i < futureConfigs.size(); ++i) {
+ zip.putNextEntry(new ZipEntry(tunnels.get(i).getName() + ".conf"));
+ zip.write(futureConfigs.get(i).getNow(null).toString().getBytes(StandardCharsets.UTF_8));
+ }
+ zip.closeEntry();
+ zip.close();
+ } catch (Exception e) {
+ file.delete();
+ throw e;
+ }
+ return file.getAbsolutePath();
+ }).whenComplete(this::exportZipComplete);
+ });
+ }
+
+ private void exportZipComplete(String filePath, Throwable throwable) {
+ if (throwable != null) {
+ final String error = ExceptionLoggers.unwrap(throwable).getMessage();
+ final String message = getContext().getString(R.string.export_error, error);
+ Log.e(TAG, message, throwable);
+ Snackbar.make(((SettingsActivity)getContext()).findViewById(android.R.id.content), message, Snackbar.LENGTH_LONG).show();
+ } else {
+ exportedFilePath = filePath;
+ setEnabled(false);
+ notifyChanged();
+ }
+ }
+
+ @Override
+ protected void onClick() {
+ ((SettingsActivity)getContext()).ensurePermissions(new String[] { "android.permission.WRITE_EXTERNAL_STORAGE" }, (permissions, granted) -> {
+ if (granted.length > 0 && granted[0] == PackageManager.PERMISSION_GRANTED)
+ exportZip();
+ });
+ }
+
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3eb72f5b..9aeb290f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -20,7 +20,7 @@
<string name="config_save_success">Successfully saved configuration for ā€œ%sā€</string>
<string name="create_activity_title">Create WireGuard Tunnel</string>
<string name="create_empty">Create from scratch</string>
- <string name="create_from_file">Create from file</string>
+ <string name="create_from_file">Create from file or archive</string>
<string name="delete">Delete</string>
<string name="dns_servers">DNS servers</string>
<string name="edit">Edit</string>
@@ -33,6 +33,10 @@
<string name="hint_generated">(generated)</string>
<string name="hint_optional">(optional)</string>
<string name="hint_random">(random)</string>
+ <string name="zip_exporter_title">Export tunnels to zip file</string>
+ <string name="export_error">Unable to export tunnels: %s</string>
+ <string name="export_success">Saved to %s</string>
+ <string name="export_summary">Zip file will be saved to downloads folder</string>
<string name="import_error">Unable to import tunnel: %s</string>
<string name="import_success">Imported ā€œ%sā€</string>
<string name="import_total_success">Imported %d tunnels</string>
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index b032bea7..c73c174b 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -6,4 +6,5 @@
android:summary="@string/restore_on_boot_summary"
android:title="@string/restore_on_boot_title" />
<com.wireguard.android.preference.ToolsInstallerPreference android:key="tools_installer" />
+ <com.wireguard.android.preference.ZipExporterPreference android:key="zip_exporter" />
</PreferenceScreen>