Allow exporting to zip file
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
parent
f4e462fabd
commit
622f41f11f
@ -5,7 +5,7 @@
|
|||||||
android:installLocation="internalOnly">
|
android:installLocation="internalOnly">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<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" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.wireguard.android.activity;
|
package com.wireguard.android.activity;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
@ -9,11 +10,60 @@ import com.wireguard.android.Application;
|
|||||||
import com.wireguard.android.R;
|
import com.wireguard.android.R;
|
||||||
import com.wireguard.android.backend.WgQuickBackend;
|
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.
|
* Interface for changing application-global persistent settings.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class SettingsActivity extends Activity {
|
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
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -20,7 +20,7 @@
|
|||||||
<string name="config_save_success">Successfully saved configuration for “%s”</string>
|
<string name="config_save_success">Successfully saved configuration for “%s”</string>
|
||||||
<string name="create_activity_title">Create WireGuard Tunnel</string>
|
<string name="create_activity_title">Create WireGuard Tunnel</string>
|
||||||
<string name="create_empty">Create from scratch</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="delete">Delete</string>
|
||||||
<string name="dns_servers">DNS servers</string>
|
<string name="dns_servers">DNS servers</string>
|
||||||
<string name="edit">Edit</string>
|
<string name="edit">Edit</string>
|
||||||
@ -33,6 +33,10 @@
|
|||||||
<string name="hint_generated">(generated)</string>
|
<string name="hint_generated">(generated)</string>
|
||||||
<string name="hint_optional">(optional)</string>
|
<string name="hint_optional">(optional)</string>
|
||||||
<string name="hint_random">(random)</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_error">Unable to import tunnel: %s</string>
|
||||||
<string name="import_success">Imported “%s”</string>
|
<string name="import_success">Imported “%s”</string>
|
||||||
<string name="import_total_success">Imported %d tunnels</string>
|
<string name="import_total_success">Imported %d tunnels</string>
|
||||||
|
@ -6,4 +6,5 @@
|
|||||||
android:summary="@string/restore_on_boot_summary"
|
android:summary="@string/restore_on_boot_summary"
|
||||||
android:title="@string/restore_on_boot_title" />
|
android:title="@string/restore_on_boot_title" />
|
||||||
<com.wireguard.android.preference.ToolsInstallerPreference android:key="tools_installer" />
|
<com.wireguard.android.preference.ToolsInstallerPreference android:key="tools_installer" />
|
||||||
|
<com.wireguard.android.preference.ZipExporterPreference android:key="zip_exporter" />
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
Loading…
Reference in New Issue
Block a user