From c3246060f53c0b751975278da98ad81fc982c6bc Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Sat, 2 Jun 2018 18:19:42 +0200 Subject: [PATCH] Preferences: add log exporter Signed-off-by: Jason A. Donenfeld --- .../preference/LogExporterPreference.java | 123 ++++++++++++++++++ .../preference/ZipExporterPreference.java | 19 +-- app/src/main/res/values/strings.xml | 10 +- app/src/main/res/xml/preferences.xml | 3 +- 4 files changed, 137 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java diff --git a/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java b/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java new file mode 100644 index 00000000..d73c41bc --- /dev/null +++ b/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java @@ -0,0 +1,123 @@ +/* + * Copyright © 2018 Samuel Holland + * Copyright © 2018 Jason A. Donenfeld . 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(); + }); + } + +} diff --git a/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java b/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java index 8013279c..70357e44 100644 --- a/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java +++ b/app/src/main/java/com/wireguard/android/preference/ZipExporterPreference.java @@ -17,12 +17,9 @@ import android.util.Log; import android.view.ContextThemeWrapper; 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; @@ -44,16 +41,10 @@ import java9.util.concurrent.CompletableFuture; 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; - @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(); } private static SettingsActivity getPrefActivity(final Preference preference) { @@ -67,7 +58,7 @@ public class ZipExporterPreference extends Preference { } private void exportZip() { - final List tunnels = new ArrayList<>(tunnelManager.getTunnels()); + final List tunnels = new ArrayList<>(Application.getComponent().getTunnelManager().getTunnels()); final List> futureConfigs = new ArrayList<>(tunnels.size()); for (final Tunnel tunnel : tunnels) futureConfigs.add(tunnel.getConfigAsync().toCompletableFuture()); @@ -76,7 +67,7 @@ public class ZipExporterPreference extends Preference { return; } CompletableFuture.allOf(futureConfigs.toArray(new CompletableFuture[futureConfigs.size()])) - .whenComplete((ignored1, exception) -> asyncWorker.supplyAsync(() -> { + .whenComplete((ignored1, exception) -> Application.getComponent().getAsyncWorker().supplyAsync(() -> { if (exception != null) throw exception; 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) { if (throwable != null) { 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); Snackbar.make( getPrefActivity(this).findViewById(android.R.id.content), @@ -118,8 +109,8 @@ public class ZipExporterPreference extends Preference { @Override public CharSequence getSummary() { return exportedFilePath == null ? - getContext().getString(R.string.export_summary) : - getContext().getString(R.string.export_success, exportedFilePath); + getContext().getString(R.string.zip_export_summary) : + getContext().getString(R.string.zip_export_success, exportedFilePath); } @Override diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 91e3fb0a..192d444c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,9 +39,6 @@ Error bringing down tunnel: %s Please obtain root access and try again Error bringing up tunnel: %s - Unable to export tunnels: %s - Saved to %s - Zip file will be saved to downloads folder Generate (auto) (generated) @@ -51,6 +48,10 @@ Imported “%s” Interface Listen port + Export log file + Unable to export log: %s + Saved to %s + Log file will be saved to downloads folder MTU Name Peer @@ -79,4 +80,7 @@ Using unknown kernel module implementation Using Go userspace implementation v%s Export tunnels to zip file + Unable to export tunnels: %s + Saved to %s + Zip file will be saved to downloads folder diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 78483f6b..680e8137 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -6,7 +6,8 @@ android:summary="@string/restore_on_boot_summary" android:title="@string/restore_on_boot_title" /> - + +