preferences: rewrite in kotlin
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
parent
90050a0008
commit
8451321a79
@ -1,94 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.wireguard.android.preference;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
import com.wireguard.android.Application;
|
|
||||||
import com.wireguard.android.R;
|
|
||||||
import com.wireguard.android.activity.SettingsActivity;
|
|
||||||
import com.wireguard.android.backend.Tunnel;
|
|
||||||
import com.wireguard.android.backend.WgQuickBackend;
|
|
||||||
import com.wireguard.util.NonNullForAll;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
import java9.util.concurrent.CompletableFuture;
|
|
||||||
import java9.util.stream.Collectors;
|
|
||||||
import java9.util.stream.StreamSupport;
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
public class KernelModuleDisablerPreference extends Preference {
|
|
||||||
private State state;
|
|
||||||
|
|
||||||
public KernelModuleDisablerPreference(final Context context, final AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
state = Application.getBackend() instanceof WgQuickBackend ? State.ENABLED : State.DISABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharSequence getSummary() {
|
|
||||||
return getContext().getString(state.summaryResourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharSequence getTitle() {
|
|
||||||
return getContext().getString(state.titleResourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ApplySharedPref")
|
|
||||||
@Override
|
|
||||||
protected void onClick() {
|
|
||||||
if (state == State.DISABLED) {
|
|
||||||
setState(State.ENABLING);
|
|
||||||
Application.getSharedPreferences().edit().putBoolean("disable_kernel_module", false).commit();
|
|
||||||
} else if (state == State.ENABLED) {
|
|
||||||
setState(State.DISABLING);
|
|
||||||
Application.getSharedPreferences().edit().putBoolean("disable_kernel_module", true).commit();
|
|
||||||
}
|
|
||||||
Application.getAsyncWorker().runAsync(() -> Application.getTunnelManager().getTunnels().thenApply(observableTunnels -> {
|
|
||||||
final Collection<CompletableFuture<Tunnel.State>> c = StreamSupport.stream(observableTunnels.values()).map(t -> t.setState(Tunnel.State.DOWN).toCompletableFuture()).collect(Collectors.toCollection(ArrayList::new));
|
|
||||||
return CompletableFuture.allOf(c.toArray(new CompletableFuture[0])).thenRun(() -> {
|
|
||||||
final Intent restartIntent = new Intent(getContext(), SettingsActivity.class);
|
|
||||||
restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
Application.get().startActivity(restartIntent);
|
|
||||||
System.exit(0);
|
|
||||||
});
|
|
||||||
}).join());
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
ENABLED(R.string.module_disabler_enabled_title, R.string.module_disabler_enabled_summary, true),
|
|
||||||
DISABLED(R.string.module_disabler_disabled_title, R.string.module_disabler_disabled_summary, true),
|
|
||||||
ENABLING(R.string.module_disabler_disabled_title, R.string.success_application_will_restart, false),
|
|
||||||
DISABLING(R.string.module_disabler_enabled_title, R.string.success_application_will_restart, false);
|
|
||||||
|
|
||||||
private final boolean shouldEnableView;
|
|
||||||
private final int summaryResourceId;
|
|
||||||
private final int titleResourceId;
|
|
||||||
|
|
||||||
State(final int titleResourceId, final int summaryResourceId, final boolean shouldEnableView) {
|
|
||||||
this.summaryResourceId = summaryResourceId;
|
|
||||||
this.titleResourceId = titleResourceId;
|
|
||||||
this.shouldEnableView = shouldEnableView;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
package com.wireguard.android.preference
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import com.wireguard.android.Application
|
||||||
|
import com.wireguard.android.R
|
||||||
|
import com.wireguard.android.activity.SettingsActivity
|
||||||
|
import com.wireguard.android.backend.Tunnel
|
||||||
|
import com.wireguard.android.backend.WgQuickBackend
|
||||||
|
import java9.util.concurrent.CompletableFuture
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
class KernelModuleDisablerPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||||
|
private var state = if (Application.getBackend() is WgQuickBackend) State.ENABLED else State.DISABLED
|
||||||
|
|
||||||
|
override fun getSummary() = context.getString(state.summaryResourceId)
|
||||||
|
|
||||||
|
override fun getTitle() = context.getString(state.titleResourceId)
|
||||||
|
|
||||||
|
@SuppressLint("ApplySharedPref")
|
||||||
|
override fun onClick() {
|
||||||
|
if (state == State.DISABLED) {
|
||||||
|
setState(State.ENABLING)
|
||||||
|
Application.getSharedPreferences().edit().putBoolean("disable_kernel_module", false).commit()
|
||||||
|
} else if (state == State.ENABLED) {
|
||||||
|
setState(State.DISABLING)
|
||||||
|
Application.getSharedPreferences().edit().putBoolean("disable_kernel_module", true).commit()
|
||||||
|
}
|
||||||
|
Application.getAsyncWorker().runAsync {
|
||||||
|
Application.getTunnelManager().tunnels.thenApply { observableTunnels ->
|
||||||
|
val downings = observableTunnels.values().map { it.setState(Tunnel.State.DOWN).toCompletableFuture() }.toTypedArray()
|
||||||
|
CompletableFuture.allOf(*downings).thenRun {
|
||||||
|
val restartIntent = Intent(context, SettingsActivity::class.java)
|
||||||
|
restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
Application.get().startActivity(restartIntent)
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
}.join()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setState(state: State) {
|
||||||
|
if (this.state == state) return
|
||||||
|
this.state = state
|
||||||
|
if (isEnabled != state.shouldEnableView) isEnabled = state.shouldEnableView
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class State(val titleResourceId: Int, val summaryResourceId: Int, val shouldEnableView: Boolean) {
|
||||||
|
ENABLED(R.string.module_disabler_enabled_title, R.string.module_disabler_enabled_summary, true),
|
||||||
|
DISABLED(R.string.module_disabler_disabled_title, R.string.module_disabler_disabled_summary, true),
|
||||||
|
ENABLING(R.string.module_disabler_disabled_title, R.string.success_application_will_restart, false),
|
||||||
|
DISABLING(R.string.module_disabler_enabled_title, R.string.success_application_will_restart, false);
|
||||||
|
}
|
||||||
|
}
|
@ -1,113 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
|
||||||
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 com.wireguard.util.NonNullForAll;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preference implementing a button that asynchronously exports logs.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import com.wireguard.android.Application
|
||||||
|
import com.wireguard.android.R
|
||||||
|
import com.wireguard.android.util.DownloadsFileSaver
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
class LogExporterPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||||
|
private var exportedFilePath: String? = null
|
||||||
|
private fun exportLog() {
|
||||||
|
Application.getAsyncWorker().supplyAsync {
|
||||||
|
val outputFile = DownloadsFileSaver.save(context, "wireguard-log.txt", "text/plain", true)
|
||||||
|
try {
|
||||||
|
val process = Runtime.getRuntime().exec(arrayOf(
|
||||||
|
"logcat", "-b", "all", "-d", "-v", "threadtime", "*:V"))
|
||||||
|
BufferedReader(InputStreamReader(process.inputStream)).use { stdout ->
|
||||||
|
BufferedReader(InputStreamReader(process.errorStream)).use { stderr ->
|
||||||
|
while (true) {
|
||||||
|
val line = stdout.readLine() ?: break
|
||||||
|
outputFile.outputStream.write(line.toByteArray())
|
||||||
|
outputFile.outputStream.write('\n'.toInt())
|
||||||
|
}
|
||||||
|
outputFile.outputStream.close()
|
||||||
|
if (process.waitFor() != 0) {
|
||||||
|
val errors = StringBuilder()
|
||||||
|
errors.append(R.string.logcat_error)
|
||||||
|
while (true) {
|
||||||
|
val line = stderr.readLine() ?: break
|
||||||
|
errors.append(line)
|
||||||
|
}
|
||||||
|
throw Exception(errors.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
outputFile.delete()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
outputFile.fileName
|
||||||
|
}.whenComplete(this::exportLogComplete)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportLogComplete(filePath: String, throwable: Throwable?) {
|
||||||
|
if (throwable != null) {
|
||||||
|
val error = ErrorMessages.get(throwable)
|
||||||
|
val message = context.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()
|
||||||
|
isEnabled = true
|
||||||
|
} else {
|
||||||
|
exportedFilePath = filePath
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSummary() = if (exportedFilePath == null)
|
||||||
|
context.getString(R.string.log_export_summary)
|
||||||
|
else
|
||||||
|
context.getString(R.string.log_export_success, exportedFilePath)
|
||||||
|
|
||||||
|
override fun getTitle() = context.getString(R.string.log_export_title)
|
||||||
|
|
||||||
|
override fun onClick() {
|
||||||
|
FragmentUtils.getPrefActivity(this).ensurePermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { _, granted ->
|
||||||
|
if (granted.isNotEmpty() && granted[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
isEnabled = false
|
||||||
|
exportLog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = "WireGuard/" + LogExporterPreference::class.java.simpleName
|
||||||
|
}
|
||||||
|
}
|
@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.wireguard.android.preference;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
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.activity.SettingsActivity;
|
|
||||||
import com.wireguard.android.util.ErrorMessages;
|
|
||||||
import com.wireguard.util.NonNullForAll;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ApplySharedPref")
|
|
||||||
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.getSharedPreferences().edit().remove("disable_kernel_module").commit();
|
|
||||||
Application.getAsyncWorker().runAsync(() -> {
|
|
||||||
final Intent restartIntent = new Intent(getContext(), SettingsActivity.class);
|
|
||||||
restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
Application.get().startActivity(restartIntent);
|
|
||||||
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.success_application_will_restart, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
package com.wireguard.android.preference
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.system.OsConstants
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import com.wireguard.android.Application
|
||||||
|
import com.wireguard.android.R
|
||||||
|
import com.wireguard.android.activity.SettingsActivity
|
||||||
|
import com.wireguard.android.util.ErrorMessages
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
class ModuleDownloaderPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||||
|
private var state = State.INITIAL
|
||||||
|
|
||||||
|
override fun getSummary() = context.getString(state.messageResourceId)
|
||||||
|
|
||||||
|
override fun getTitle() = context.getString(R.string.module_installer_title)
|
||||||
|
|
||||||
|
override fun onClick() {
|
||||||
|
setState(State.WORKING)
|
||||||
|
Application.getAsyncWorker().supplyAsync(Application.getModuleLoader()::download).whenComplete(this::onDownloadResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ApplySharedPref")
|
||||||
|
private fun onDownloadResult(result: Int, throwable: Throwable?) {
|
||||||
|
when {
|
||||||
|
throwable != null -> {
|
||||||
|
setState(State.FAILURE)
|
||||||
|
Toast.makeText(context, ErrorMessages.get(throwable), Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
result == OsConstants.ENOENT -> setState(State.NOTFOUND)
|
||||||
|
result == OsConstants.EXIT_SUCCESS -> {
|
||||||
|
setState(State.SUCCESS)
|
||||||
|
Application.getSharedPreferences().edit().remove("disable_kernel_module").commit()
|
||||||
|
Application.getAsyncWorker().runAsync {
|
||||||
|
val restartIntent = Intent(context, SettingsActivity::class.java)
|
||||||
|
restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
Application.get().startActivity(restartIntent)
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> setState(State.FAILURE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setState(state: State) {
|
||||||
|
if (this.state == state) return
|
||||||
|
this.state = state
|
||||||
|
if (isEnabled != state.shouldEnableView) isEnabled = state.shouldEnableView
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class State(val messageResourceId: Int, val shouldEnableView: Boolean) {
|
||||||
|
INITIAL(R.string.module_installer_initial, true),
|
||||||
|
FAILURE(R.string.module_installer_error, true),
|
||||||
|
WORKING(R.string.module_installer_working, false),
|
||||||
|
SUCCESS(R.string.success_application_will_restart, false),
|
||||||
|
NOTFOUND(R.string.module_installer_not_found, false);
|
||||||
|
}
|
||||||
|
}
|
@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.wireguard.android.preference;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
import com.wireguard.android.Application;
|
|
||||||
import com.wireguard.android.R;
|
|
||||||
import com.wireguard.android.util.ToolsInstaller;
|
|
||||||
import com.wireguard.util.NonNullForAll;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preference implementing a button that asynchronously runs {@code ToolsInstaller} and displays the
|
|
||||||
* result as the preference summary.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import com.wireguard.android.Application
|
||||||
|
import com.wireguard.android.R
|
||||||
|
import com.wireguard.android.util.ToolsInstaller
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preference implementing a button that asynchronously runs `ToolsInstaller` and displays the
|
||||||
|
* result as the preference summary.
|
||||||
|
*/
|
||||||
|
class ToolsInstallerPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||||
|
private var state = State.INITIAL
|
||||||
|
|
||||||
|
override fun getSummary() = context.getString(state.messageResourceId)
|
||||||
|
|
||||||
|
override fun getTitle() = context.getString(R.string.tools_installer_title)
|
||||||
|
|
||||||
|
override fun onAttached() {
|
||||||
|
super.onAttached()
|
||||||
|
Application.getAsyncWorker().supplyAsync(Application.getToolsInstaller()::areInstalled).whenComplete(this::onCheckResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCheckResult(state: Int, throwable: Throwable?) {
|
||||||
|
when {
|
||||||
|
throwable != null || state == ToolsInstaller.ERROR -> setState(State.INITIAL)
|
||||||
|
state and ToolsInstaller.YES == ToolsInstaller.YES -> setState(State.ALREADY)
|
||||||
|
state and (ToolsInstaller.MAGISK or ToolsInstaller.NO) == ToolsInstaller.MAGISK or ToolsInstaller.NO -> setState(State.INITIAL_MAGISK)
|
||||||
|
state and (ToolsInstaller.SYSTEM or ToolsInstaller.NO) == ToolsInstaller.SYSTEM or ToolsInstaller.NO -> setState(State.INITIAL_SYSTEM)
|
||||||
|
else -> setState(State.INITIAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick() {
|
||||||
|
setState(State.WORKING)
|
||||||
|
Application.getAsyncWorker().supplyAsync { Application.getToolsInstaller().install() }.whenComplete { result: Int, throwable: Throwable? -> onInstallResult(result, throwable) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onInstallResult(result: Int, throwable: Throwable?) {
|
||||||
|
when {
|
||||||
|
throwable != null -> setState(State.FAILURE)
|
||||||
|
result and (ToolsInstaller.YES or ToolsInstaller.MAGISK) == ToolsInstaller.YES or ToolsInstaller.MAGISK -> setState(State.SUCCESS_MAGISK)
|
||||||
|
result and (ToolsInstaller.YES or ToolsInstaller.SYSTEM) == ToolsInstaller.YES or ToolsInstaller.SYSTEM -> setState(State.SUCCESS_SYSTEM)
|
||||||
|
else -> setState(State.FAILURE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setState(state: State) {
|
||||||
|
if (this.state == state) return
|
||||||
|
this.state = state
|
||||||
|
if (isEnabled != state.shouldEnableView) isEnabled = state.shouldEnableView
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class State(val messageResourceId: Int, val shouldEnableView: Boolean) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 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 com.wireguard.util.NonNullForAll;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
public class VersionPreference extends Preference {
|
|
||||||
@Nullable private String versionSummary;
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getBackendPrettyName(final Context context, final Backend backend) {
|
|
||||||
if (backend instanceof WgQuickBackend)
|
|
||||||
return context.getString(R.string.type_name_kernel_module);
|
|
||||||
if (backend instanceof GoBackend)
|
|
||||||
return context.getString(R.string.type_name_go_userspace);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@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) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.util.AttributeSet
|
||||||
|
import androidx.preference.Preference
|
||||||
|
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
|
||||||
|
|
||||||
|
class VersionPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||||
|
private var versionSummary: String? = null
|
||||||
|
|
||||||
|
override fun getSummary() = versionSummary
|
||||||
|
|
||||||
|
override fun getTitle() = context.getString(R.string.version_title, BuildConfig.VERSION_NAME)
|
||||||
|
|
||||||
|
override fun onClick() {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.data = Uri.parse("https://www.wireguard.com/")
|
||||||
|
try {
|
||||||
|
context.startActivity(intent)
|
||||||
|
} catch (_: ActivityNotFoundException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun getBackendPrettyName(context: Context, backend: Backend) = when (backend) {
|
||||||
|
is WgQuickBackend -> context.getString(R.string.type_name_kernel_module)
|
||||||
|
is GoBackend -> context.getString(R.string.type_name_go_userspace)
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
Application.getBackendAsync().thenAccept { backend: Backend ->
|
||||||
|
versionSummary = getContext().getString(R.string.version_summary_checking, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH))
|
||||||
|
Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete { version, exception ->
|
||||||
|
versionSummary = if (exception == null)
|
||||||
|
getContext().getString(R.string.version_summary, getBackendPrettyName(context, backend), version)
|
||||||
|
else
|
||||||
|
getContext().getString(R.string.version_summary_unknown, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH))
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,121 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
|
||||||
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 com.wireguard.util.NonNullForAll;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipOutputStream;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
import java9.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preference implementing a button that asynchronously exports config zips.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
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.ErrorMessages
|
||||||
|
import com.wireguard.android.util.FragmentUtils
|
||||||
|
import com.wireguard.android.util.ObservableSortedKeyedList
|
||||||
|
import com.wireguard.config.Config
|
||||||
|
import java9.util.concurrent.CompletableFuture
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preference implementing a button that asynchronously exports config zips.
|
||||||
|
*/
|
||||||
|
class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
|
||||||
|
private var exportedFilePath: String? = null
|
||||||
|
|
||||||
|
private fun exportZip() {
|
||||||
|
Application.getTunnelManager().tunnels.thenAccept(this::exportZip)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportZip(tunnels: List<ObservableTunnel>) {
|
||||||
|
val futureConfigs = tunnels.map { it.configAsync.toCompletableFuture() }.toTypedArray()
|
||||||
|
if (futureConfigs.isEmpty()) {
|
||||||
|
exportZipComplete(null, IllegalArgumentException(
|
||||||
|
context.getString(R.string.no_tunnels_error)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
CompletableFuture.allOf(*futureConfigs)
|
||||||
|
.whenComplete { _, exception ->
|
||||||
|
Application.getAsyncWorker().supplyAsync {
|
||||||
|
if (exception != null) throw exception
|
||||||
|
val outputFile = DownloadsFileSaver.save(context, "wireguard-export.zip", "application/zip", true)
|
||||||
|
try {
|
||||||
|
ZipOutputStream(outputFile.outputStream).use { zip ->
|
||||||
|
for (i in futureConfigs.indices) {
|
||||||
|
zip.putNextEntry(ZipEntry(tunnels[i].name + ".conf"))
|
||||||
|
zip.write(futureConfigs[i].getNow(null)!!.toWgQuickString().toByteArray(StandardCharsets.UTF_8))
|
||||||
|
}
|
||||||
|
zip.closeEntry()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
outputFile.delete()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
outputFile.fileName
|
||||||
|
}.whenComplete(this::exportZipComplete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportZipComplete(filePath: String?, throwable: Throwable?) {
|
||||||
|
if (throwable != null) {
|
||||||
|
val error = ErrorMessages.get(throwable)
|
||||||
|
val message = context.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()
|
||||||
|
isEnabled = true
|
||||||
|
} else {
|
||||||
|
exportedFilePath = filePath
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSummary() = if (exportedFilePath == null) context.getString(R.string.zip_export_summary) else context.getString(R.string.zip_export_success, exportedFilePath)
|
||||||
|
|
||||||
|
override fun getTitle() = context.getString(R.string.zip_export_title)
|
||||||
|
|
||||||
|
override fun onClick() {
|
||||||
|
FragmentUtils.getPrefActivity(this).ensurePermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { _, granted ->
|
||||||
|
if (granted.isNotEmpty() && granted[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
isEnabled = false
|
||||||
|
exportZip()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = "WireGuard/" + ZipExporterPreference::class.java.simpleName
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user