Preferences: add log exporter
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
parent
b7e025e381
commit
c3246060f5
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||||
|
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.wireguard.android.preference;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v7.preference.Preference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.ContextThemeWrapper;
|
||||||
|
|
||||||
|
import com.wireguard.android.Application;
|
||||||
|
import com.wireguard.android.R;
|
||||||
|
import com.wireguard.android.activity.SettingsActivity;
|
||||||
|
import com.wireguard.android.util.ExceptionLoggers;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
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();
|
||||||
|
|
||||||
|
private String exportedFilePath;
|
||||||
|
|
||||||
|
public LogExporterPreference(final Context context, final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SettingsActivity getPrefActivity(final Preference preference) {
|
||||||
|
final Context context = preference.getContext();
|
||||||
|
if (context instanceof ContextThemeWrapper) {
|
||||||
|
if (((ContextThemeWrapper) context).getBaseContext() instanceof SettingsActivity) {
|
||||||
|
return ((SettingsActivity) ((ContextThemeWrapper) context).getBaseContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportLog() {
|
||||||
|
Application.getComponent().getAsyncWorker().supplyAsync(() -> {
|
||||||
|
final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||||
|
final File file = new File(path, "wireguard-log.txt");
|
||||||
|
if (!path.isDirectory() && !path.mkdirs())
|
||||||
|
throw new IOException("Cannot create output directory");
|
||||||
|
|
||||||
|
/* We would like to simply run `builder.redirectOutput(file);`, but this is API 26.
|
||||||
|
* Instead we have to do this dance, since logcat appends.
|
||||||
|
*/
|
||||||
|
new FileOutputStream(file).close();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final Process process = Runtime.getRuntime().exec(new String[]{
|
||||||
|
"logcat", "-b", "all", "-d", "-v", "threadtime", "--pid", Integer.toString(android.os.Process.myPid()), "-f", file.getAbsolutePath(), "*:V"});
|
||||||
|
if (process.waitFor() != 0) {
|
||||||
|
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
|
||||||
|
final StringBuilder errors = new StringBuilder();
|
||||||
|
errors.append("Unable to run logcat: ");
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null)
|
||||||
|
errors.append(line);
|
||||||
|
throw new Exception(errors.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
file.delete();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return file.getAbsolutePath();
|
||||||
|
}).whenComplete(this::exportLogComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportLogComplete(final String filePath, final Throwable throwable) {
|
||||||
|
if (throwable != null) {
|
||||||
|
final String error = ExceptionLoggers.unwrapMessage(throwable);
|
||||||
|
final String message = getContext().getString(R.string.log_export_error, error);
|
||||||
|
Log.e(TAG, message, throwable);
|
||||||
|
Snackbar.make(
|
||||||
|
getPrefActivity(this).findViewById(android.R.id.content),
|
||||||
|
message, Snackbar.LENGTH_LONG).show();
|
||||||
|
} else {
|
||||||
|
exportedFilePath = filePath;
|
||||||
|
setEnabled(false);
|
||||||
|
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_exporter_title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onClick() {
|
||||||
|
getPrefActivity(this).ensurePermissions(
|
||||||
|
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||||
|
(permissions, granted) -> {
|
||||||
|
if (granted.length > 0 && granted[0] == PackageManager.PERMISSION_GRANTED)
|
||||||
|
exportLog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,12 +17,9 @@ import android.util.Log;
|
|||||||
import android.view.ContextThemeWrapper;
|
import android.view.ContextThemeWrapper;
|
||||||
|
|
||||||
import com.wireguard.android.Application;
|
import com.wireguard.android.Application;
|
||||||
import com.wireguard.android.Application.ApplicationComponent;
|
|
||||||
import com.wireguard.android.R;
|
import com.wireguard.android.R;
|
||||||
import com.wireguard.android.activity.SettingsActivity;
|
import com.wireguard.android.activity.SettingsActivity;
|
||||||
import com.wireguard.android.model.Tunnel;
|
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.android.util.ExceptionLoggers;
|
||||||
import com.wireguard.config.Config;
|
import com.wireguard.config.Config;
|
||||||
|
|
||||||
@ -44,16 +41,10 @@ import java9.util.concurrent.CompletableFuture;
|
|||||||
public class ZipExporterPreference extends Preference {
|
public class ZipExporterPreference extends Preference {
|
||||||
private static final String TAG = "WireGuard/" + ZipExporterPreference.class.getSimpleName();
|
private static final String TAG = "WireGuard/" + ZipExporterPreference.class.getSimpleName();
|
||||||
|
|
||||||
private final AsyncWorker asyncWorker;
|
|
||||||
private final TunnelManager tunnelManager;
|
|
||||||
private String exportedFilePath;
|
private String exportedFilePath;
|
||||||
|
|
||||||
@SuppressWarnings({"SameParameterValue", "WeakerAccess"})
|
|
||||||
public ZipExporterPreference(final Context context, final AttributeSet attrs) {
|
public ZipExporterPreference(final Context context, final AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
final ApplicationComponent applicationComponent = Application.getComponent();
|
|
||||||
asyncWorker = applicationComponent.getAsyncWorker();
|
|
||||||
tunnelManager = applicationComponent.getTunnelManager();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SettingsActivity getPrefActivity(final Preference preference) {
|
private static SettingsActivity getPrefActivity(final Preference preference) {
|
||||||
@ -67,7 +58,7 @@ public class ZipExporterPreference extends Preference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void exportZip() {
|
private void exportZip() {
|
||||||
final List<Tunnel> tunnels = new ArrayList<>(tunnelManager.getTunnels());
|
final List<Tunnel> tunnels = new ArrayList<>(Application.getComponent().getTunnelManager().getTunnels());
|
||||||
final List<CompletableFuture<Config>> futureConfigs = new ArrayList<>(tunnels.size());
|
final List<CompletableFuture<Config>> futureConfigs = new ArrayList<>(tunnels.size());
|
||||||
for (final Tunnel tunnel : tunnels)
|
for (final Tunnel tunnel : tunnels)
|
||||||
futureConfigs.add(tunnel.getConfigAsync().toCompletableFuture());
|
futureConfigs.add(tunnel.getConfigAsync().toCompletableFuture());
|
||||||
@ -76,7 +67,7 @@ public class ZipExporterPreference extends Preference {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CompletableFuture.allOf(futureConfigs.toArray(new CompletableFuture[futureConfigs.size()]))
|
CompletableFuture.allOf(futureConfigs.toArray(new CompletableFuture[futureConfigs.size()]))
|
||||||
.whenComplete((ignored1, exception) -> asyncWorker.supplyAsync(() -> {
|
.whenComplete((ignored1, exception) -> Application.getComponent().getAsyncWorker().supplyAsync(() -> {
|
||||||
if (exception != null)
|
if (exception != null)
|
||||||
throw exception;
|
throw exception;
|
||||||
final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||||
@ -103,7 +94,7 @@ public class ZipExporterPreference extends Preference {
|
|||||||
private void exportZipComplete(final String filePath, final Throwable throwable) {
|
private void exportZipComplete(final String filePath, final Throwable throwable) {
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
final String error = ExceptionLoggers.unwrapMessage(throwable);
|
final String error = ExceptionLoggers.unwrapMessage(throwable);
|
||||||
final String message = getContext().getString(R.string.export_error, error);
|
final String message = getContext().getString(R.string.zip_export_error, error);
|
||||||
Log.e(TAG, message, throwable);
|
Log.e(TAG, message, throwable);
|
||||||
Snackbar.make(
|
Snackbar.make(
|
||||||
getPrefActivity(this).findViewById(android.R.id.content),
|
getPrefActivity(this).findViewById(android.R.id.content),
|
||||||
@ -118,8 +109,8 @@ public class ZipExporterPreference extends Preference {
|
|||||||
@Override
|
@Override
|
||||||
public CharSequence getSummary() {
|
public CharSequence getSummary() {
|
||||||
return exportedFilePath == null ?
|
return exportedFilePath == null ?
|
||||||
getContext().getString(R.string.export_summary) :
|
getContext().getString(R.string.zip_export_summary) :
|
||||||
getContext().getString(R.string.export_success, exportedFilePath);
|
getContext().getString(R.string.zip_export_success, exportedFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -39,9 +39,6 @@
|
|||||||
<string name="error_down">Error bringing down tunnel: %s</string>
|
<string name="error_down">Error bringing down tunnel: %s</string>
|
||||||
<string name="error_root">Please obtain root access and try again</string>
|
<string name="error_root">Please obtain root access and try again</string>
|
||||||
<string name="error_up">Error bringing up tunnel: %s</string>
|
<string name="error_up">Error bringing up tunnel: %s</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="generate">Generate</string>
|
<string name="generate">Generate</string>
|
||||||
<string name="hint_automatic">(auto)</string>
|
<string name="hint_automatic">(auto)</string>
|
||||||
<string name="hint_generated">(generated)</string>
|
<string name="hint_generated">(generated)</string>
|
||||||
@ -51,6 +48,10 @@
|
|||||||
<string name="import_success">Imported “%s”</string>
|
<string name="import_success">Imported “%s”</string>
|
||||||
<string name="interface_title">Interface</string>
|
<string name="interface_title">Interface</string>
|
||||||
<string name="listen_port">Listen port</string>
|
<string name="listen_port">Listen port</string>
|
||||||
|
<string name="log_exporter_title">Export log file</string>
|
||||||
|
<string name="log_export_error">Unable to export log: %s</string>
|
||||||
|
<string name="log_export_success">Saved to %s</string>
|
||||||
|
<string name="log_export_summary">Log file will be saved to downloads folder</string>
|
||||||
<string name="mtu">MTU</string>
|
<string name="mtu">MTU</string>
|
||||||
<string name="name">Name</string>
|
<string name="name">Name</string>
|
||||||
<string name="peer">Peer</string>
|
<string name="peer">Peer</string>
|
||||||
@ -79,4 +80,7 @@
|
|||||||
<string name="version_kernel_unknown_summary">Using unknown kernel module implementation</string>
|
<string name="version_kernel_unknown_summary">Using unknown kernel module implementation</string>
|
||||||
<string name="version_userspace_summary">Using Go userspace implementation v%s</string>
|
<string name="version_userspace_summary">Using Go userspace implementation v%s</string>
|
||||||
<string name="zip_exporter_title">Export tunnels to zip file</string>
|
<string name="zip_exporter_title">Export tunnels to zip file</string>
|
||||||
|
<string name="zip_export_error">Unable to export tunnels: %s</string>
|
||||||
|
<string name="zip_export_success">Saved to %s</string>
|
||||||
|
<string name="zip_export_summary">Zip file will be saved to downloads folder</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
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" />
|
<com.wireguard.android.preference.ZipExporterPreference />
|
||||||
|
<com.wireguard.android.preference.LogExporterPreference />
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="dark_theme"
|
android:key="dark_theme"
|
||||||
|
Loading…
Reference in New Issue
Block a user