From 81ed5978d6c348ab65b186af318ffc21079ab6fe Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 3 Jul 2020 11:51:26 +0100 Subject: [PATCH] Add buttons for exporting persisted logs to SD card. --- .../briar/android/activity/RequestCodes.java | 2 + .../android/settings/SettingsActivity.java | 132 +++++++++++++++++- .../android/settings/SettingsFragment.java | 22 ++- briar-android/src/main/res/xml/settings.xml | 10 ++ 4 files changed, 164 insertions(+), 2 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java index 3e1351ab3..c581c3c62 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/RequestCodes.java @@ -16,5 +16,7 @@ public interface RequestCodes { int REQUEST_KEYGUARD_UNLOCK = 12; int REQUEST_ATTACH_IMAGE = 13; int REQUEST_SAVE_ATTACHMENT = 14; + int REQUEST_EXPORT_LOG = 15; + int REQUEST_EXPORT_OLD_LOG = 16; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java index 311711b1b..f21331c91 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java @@ -1,18 +1,64 @@ package org.briarproject.briar.android.settings; +import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.view.MenuItem; +import android.widget.Toast; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; +import org.briarproject.briar.api.logging.PersistentLogManager; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import androidx.annotation.RequiresApi; import androidx.appcompat.app.ActionBar; +import static android.content.Intent.ACTION_CREATE_DOCUMENT; +import static android.content.Intent.CATEGORY_OPENABLE; +import static android.content.Intent.EXTRA_TITLE; +import static android.os.Build.VERSION.SDK_INT; +import static android.os.Environment.DIRECTORY_DOWNLOADS; +import static android.os.Environment.getExternalStoragePublicDirectory; +import static android.widget.Toast.LENGTH_LONG; +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_EXPORT_LOG; +import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_EXPORT_OLD_LOG; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault public class SettingsActivity extends BriarActivity { + private static final Logger LOG = + getLogger(SettingsActivity.class.getName()); + + private static final String LOG_EXPORT_FILENAME = "briar-log.txt"; + + @Inject + @IoExecutor + Executor ioExecutor; + + @Inject + PersistentLogManager logManager; + @Override - public void onCreate(Bundle bundle) { + public void onCreate(@Nullable Bundle bundle) { super.onCreate(bundle); ActionBar actionBar = getSupportActionBar(); @@ -37,4 +83,88 @@ public class SettingsActivity extends BriarActivity { } return false; } + + @Override + protected void onActivityResult(int request, int result, + @Nullable Intent data) { + super.onActivityResult(request, result, data); + if (request == REQUEST_EXPORT_LOG && result == RESULT_OK && + data != null && data.getData() != null) { + exportLog(false, data.getData()); + } else if (request == REQUEST_EXPORT_OLD_LOG && result == RESULT_OK && + data != null && data.getData() != null) { + exportLog(true, data.getData()); + } + } + + private void exportLog(boolean old, Uri uri) { + copyLog(old, () -> getOutputStream(uri)); + } + + private void copyLog(boolean old, OutputStreamProvider osp) { + ioExecutor.execute(() -> { + try { + PrintWriter w = new PrintWriter(osp.getOutputStream()); + File logDir = getApplication().getDir("log", MODE_PRIVATE); + for (String line : logManager.getPersistedLog(logDir, old)) { + w.println(line); + } + w.close(); + runOnUiThreadUnlessDestroyed(() -> + Toast.makeText(getApplication(), "Log exported", + LENGTH_LONG).show()); + } catch (IOException e) { + logException(LOG, WARNING, e); + runOnUiThreadUnlessDestroyed(() -> + Toast.makeText(getApplication(), "Failed to export log", + LENGTH_LONG).show()); + } + }); + } + + void onExportLogClick(boolean old) { + if (SDK_INT >= 19) { + Intent intent = getExportLogIntent(); + int request = old ? REQUEST_EXPORT_OLD_LOG : REQUEST_EXPORT_LOG; + startActivityForResult(intent, request); + } else { + exportLog(old); + } + } + + @RequiresApi(api = 19) + private Intent getExportLogIntent() { + Intent intent = new Intent(ACTION_CREATE_DOCUMENT); + intent.addCategory(CATEGORY_OPENABLE); + intent.setType("text/plain"); + intent.putExtra(EXTRA_TITLE, LOG_EXPORT_FILENAME); + return intent; + } + + private void exportLog(boolean old) { + File file = getLogOutputFile(); + copyLog(old, () -> getOutputStream(file)); + } + + private File getLogOutputFile() { + File path = getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS); + //noinspection ResultOfMethodCallIgnored + path.mkdirs(); + return new File(path, LOG_EXPORT_FILENAME); + } + + private OutputStream getOutputStream(File file) throws IOException { + return new FileOutputStream(file); + } + + private OutputStream getOutputStream(Uri uri) throws IOException { + OutputStream os = + getApplication().getContentResolver().openOutputStream(uri); + if (os == null) throw new IOException(); + return os; + } + + private interface OutputStreamProvider { + OutputStream getOutputStream() throws IOException; + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java index c09f91190..96fb1d850 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java @@ -1,5 +1,6 @@ package org.briarproject.briar.android.settings; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.AlertDialog; import android.content.Context; @@ -74,6 +75,7 @@ import static android.widget.Toast.LENGTH_SHORT; import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE; import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_MOBILE; import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_NETWORK; @@ -133,7 +135,7 @@ public class SettingsFragment extends PreferenceFragmentCompat "pref_key_tor_only_when_charging"; private static final Logger LOG = - Logger.getLogger(SettingsFragment.class.getName()); + getLogger(SettingsFragment.class.getName()); private SettingsActivity listener; private ListPreference language; @@ -251,8 +253,25 @@ public class SettingsFragment extends PreferenceFragmentCompat throw new RuntimeException("Boom!"); } ); + findPreference("pref_key_export_log").setOnPreferenceClickListener( + preference -> { + ((SettingsActivity) requireActivity()) + .onExportLogClick(false); + return true; + } + ); + findPreference("pref_key_export_old_log") + .setOnPreferenceClickListener( + preference -> { + ((SettingsActivity) requireActivity()) + .onExportLogClick(true); + return true; + } + ); } else { findPreference("pref_key_explode").setVisible(false); + findPreference("pref_key_export_log").setVisible(false); + findPreference("pref_key_export_old_log").setVisible(false); findPreference("pref_key_test_data").setVisible(false); PreferenceGroup testing = findPreference("pref_key_explode").getParent(); @@ -331,6 +350,7 @@ public class SettingsFragment extends PreferenceFragmentCompat return direction == LAYOUT_DIRECTION_LTR; } + @SuppressLint("StringFormatInvalid") private void setTorNetworkSummary(int torNetworkSetting) { if (torNetworkSetting != PREF_TOR_NETWORK_AUTOMATIC) { torNetwork.setSummary("%s"); // use setting value diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml index 21acdd98f..70c6c75e4 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -227,6 +227,16 @@ android:targetPackage="@string/app_package"/> + + + +