mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-16 04:39:54 +01:00
Save encrypted logs to disk on debug builds.
This commit is contained in:
@@ -3,9 +3,11 @@ package org.briarproject.bramble.account;
|
||||
import android.app.Application;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.briarproject.bramble.api.FeatureFlags;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.Localizer;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
@@ -15,10 +17,16 @@ import javax.inject.Inject;
|
||||
class BriarAccountManager extends AndroidAccountManager {
|
||||
|
||||
@Inject
|
||||
BriarAccountManager(DatabaseConfig databaseConfig, CryptoComponent crypto,
|
||||
IdentityManager identityManager, SharedPreferences prefs,
|
||||
BriarAccountManager(
|
||||
DatabaseConfig databaseConfig,
|
||||
CryptoComponent crypto,
|
||||
IdentityManager identityManager,
|
||||
SharedPreferences prefs,
|
||||
PersistentLogManager logManager,
|
||||
FeatureFlags featureFlags,
|
||||
Application app) {
|
||||
super(databaseConfig, crypto, identityManager, prefs, app);
|
||||
super(databaseConfig, crypto, identityManager, prefs, logManager,
|
||||
featureFlags, app);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
@@ -78,6 +79,7 @@ import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager
|
||||
import org.briarproject.briar.api.test.TestDataCreator;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Formatter;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -204,6 +206,10 @@ public interface AndroidComponent
|
||||
|
||||
AutoDeleteManager autoDeleteManager();
|
||||
|
||||
PersistentLogManager persistentLogManager();
|
||||
|
||||
Formatter formatter();
|
||||
|
||||
void inject(SignInReminderReceiver briarService);
|
||||
|
||||
void inject(BriarService briarService);
|
||||
|
||||
@@ -245,8 +245,9 @@ public class AppModule {
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getLogcatFile() {
|
||||
return AndroidUtils.getLogcatFile(app.getApplicationContext());
|
||||
public File getTemporaryLogFile() {
|
||||
return AndroidUtils
|
||||
.getTemporaryLogFile(app.getApplicationContext());
|
||||
}
|
||||
};
|
||||
return devConfig;
|
||||
@@ -337,6 +338,11 @@ public class AppModule {
|
||||
public boolean shouldEnableDisappearingMessages() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnablePersistentLogs() {
|
||||
return IS_DEBUG_BUILD;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,14 @@ import com.vanniktech.emoji.google.GoogleEmojiProvider;
|
||||
import org.briarproject.bramble.BrambleAndroidEagerSingletons;
|
||||
import org.briarproject.bramble.BrambleAppComponent;
|
||||
import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
||||
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.logging.CachingLogHandler;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Logger;
|
||||
@@ -31,7 +34,10 @@ import androidx.annotation.NonNull;
|
||||
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
||||
import static java.util.logging.Level.FINE;
|
||||
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.util.AndroidUtils.getPersistentLogDir;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
|
||||
public class BriarApplicationImpl extends Application
|
||||
@@ -81,6 +87,17 @@ public class BriarApplicationImpl extends Application
|
||||
rootLogger.addHandler(logHandler);
|
||||
rootLogger.setLevel(IS_DEBUG_BUILD ? FINE : INFO);
|
||||
|
||||
if (applicationComponent.featureFlags().shouldEnablePersistentLogs()) {
|
||||
PersistentLogManager logManager =
|
||||
applicationComponent.persistentLogManager();
|
||||
File logDir = getPersistentLogDir(this);
|
||||
try {
|
||||
rootLogger.addHandler(logManager.createLogHandler(logDir));
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
LOG.info("Created");
|
||||
|
||||
EmojiManager.install(new GoogleEmojiProvider());
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
package org.briarproject.briar.android.logging;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.Locale.US;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
public class BriefLogFormatter extends Formatter {
|
||||
|
||||
public static String formatLog(Formatter formatter,
|
||||
Collection<LogRecord> logRecords) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (LogRecord record : logRecords) {
|
||||
String formatted = formatter.format(record);
|
||||
sb.append(formatted).append('\n');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private final Object lock = new Object();
|
||||
private final DateFormat dateFormat; // Locking: lock
|
||||
private final Date date; // Locking: lock
|
||||
|
||||
public BriefLogFormatter() {
|
||||
synchronized (lock) {
|
||||
dateFormat = new SimpleDateFormat("MM-dd HH:mm:ss.SSS ", US);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
date = new Date();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(LogRecord record) {
|
||||
String dateString;
|
||||
synchronized (lock) {
|
||||
date.setTime(record.getMillis());
|
||||
dateString = dateFormat.format(date);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(dateString);
|
||||
sb.append(record.getLevel().getName().charAt(0)).append('/');
|
||||
String tag = record.getLoggerName();
|
||||
tag = tag.substring(tag.lastIndexOf('.') + 1);
|
||||
sb.append(tag).append(": ");
|
||||
sb.append(record.getMessage());
|
||||
Throwable t = record.getThrown();
|
||||
if (t != null) {
|
||||
sb.append('\n');
|
||||
appendThrowable(sb, t);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void appendThrowable(StringBuilder sb, Throwable t) {
|
||||
sb.append(t);
|
||||
for (StackTraceElement e : t.getStackTrace())
|
||||
sb.append("\n at ").append(e);
|
||||
Throwable cause = t.getCause();
|
||||
if (cause != null) {
|
||||
sb.append("\n Caused by: ");
|
||||
appendThrowable(sb, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,9 @@ import androidx.annotation.Nullable;
|
||||
@NotNullByDefault
|
||||
public interface LogDecrypter {
|
||||
/**
|
||||
* Returns decrypted log records from {@link AndroidUtils#getLogcatFile}
|
||||
* or null if there was an error reading the logs.
|
||||
* Returns decrypted log records from
|
||||
* {@link AndroidUtils#getTemporaryLogFile} or null if there was an error
|
||||
* reading the logs.
|
||||
*/
|
||||
@Nullable
|
||||
String decryptLogs(@Nullable byte[] logKey);
|
||||
|
||||
@@ -41,7 +41,7 @@ class LogDecrypterImpl implements LogDecrypter {
|
||||
public String decryptLogs(@Nullable byte[] logKey) {
|
||||
if (logKey == null) return null;
|
||||
SecretKey key = new SecretKey(logKey);
|
||||
File logFile = devConfig.getLogcatFile();
|
||||
File logFile = devConfig.getTemporaryLogFile();
|
||||
try (InputStream in = new FileInputStream(logFile)) {
|
||||
InputStream reader =
|
||||
streamReaderFactory.createLogStreamReader(in, key);
|
||||
|
||||
@@ -8,7 +8,7 @@ import androidx.annotation.Nullable;
|
||||
@NotNullByDefault
|
||||
public interface LogEncrypter {
|
||||
/**
|
||||
* Writes encrypted log records to {@link AndroidUtils#getLogcatFile}
|
||||
* Writes encrypted log records to {@link AndroidUtils#getTemporaryLogFile}
|
||||
* and returns the encryption key if everything went fine.
|
||||
*/
|
||||
@Nullable
|
||||
|
||||
@@ -33,16 +33,19 @@ class LogEncrypterImpl implements LogEncrypter {
|
||||
|
||||
private final DevConfig devConfig;
|
||||
private final CachingLogHandler logHandler;
|
||||
private final Formatter formatter;
|
||||
private final CryptoComponent crypto;
|
||||
private final StreamWriterFactory streamWriterFactory;
|
||||
|
||||
@Inject
|
||||
LogEncrypterImpl(DevConfig devConfig,
|
||||
CachingLogHandler logHandler,
|
||||
Formatter formatter,
|
||||
CryptoComponent crypto,
|
||||
StreamWriterFactory streamWriterFactory) {
|
||||
this.devConfig = devConfig;
|
||||
this.logHandler = logHandler;
|
||||
this.formatter = formatter;
|
||||
this.crypto = crypto;
|
||||
this.streamWriterFactory = streamWriterFactory;
|
||||
}
|
||||
@@ -51,7 +54,7 @@ class LogEncrypterImpl implements LogEncrypter {
|
||||
@Override
|
||||
public byte[] encryptLogs() {
|
||||
SecretKey logKey = crypto.generateSecretKey();
|
||||
File logFile = devConfig.getLogcatFile();
|
||||
File logFile = devConfig.getTemporaryLogFile();
|
||||
try (OutputStream out = new FileOutputStream(logFile)) {
|
||||
StreamWriter streamWriter =
|
||||
streamWriterFactory.createLogStreamWriter(out, logKey);
|
||||
@@ -67,10 +70,8 @@ class LogEncrypterImpl implements LogEncrypter {
|
||||
}
|
||||
|
||||
private void writeLogString(Writer writer) throws IOException {
|
||||
Formatter formatter = new BriefLogFormatter();
|
||||
for (LogRecord record : logHandler.getRecentLogRecords()) {
|
||||
String formatted = formatter.format(record);
|
||||
writer.append(formatted).append('\n');
|
||||
writer.append(formatter.format(record)).append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.BuildConfig;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
||||
import org.briarproject.briar.android.reporting.ReportData.ReportInfo;
|
||||
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
|
||||
import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo;
|
||||
|
||||
@@ -71,7 +72,7 @@ class BriarReportCollector {
|
||||
}
|
||||
|
||||
ReportData collectReportData(@Nullable Throwable t, long appStartTime,
|
||||
String logs) {
|
||||
ReportInfo logs) {
|
||||
ReportData reportData = new ReportData()
|
||||
.add(getBasicInfo(t))
|
||||
.add(getDeviceInfo());
|
||||
@@ -82,7 +83,7 @@ class BriarReportCollector {
|
||||
.add(getStorage())
|
||||
.add(getConnectivity())
|
||||
.add(getBuildConfig())
|
||||
.add(getLogcat(logs))
|
||||
.add(getLogs(logs))
|
||||
.add(getDeviceFeatures());
|
||||
}
|
||||
|
||||
@@ -309,8 +310,8 @@ class BriarReportCollector {
|
||||
buildConfig);
|
||||
}
|
||||
|
||||
private ReportItem getLogcat(String logs) {
|
||||
return new ReportItem("Logcat", R.string.dev_report_logcat, logs);
|
||||
private ReportItem getLogs(ReportInfo logs) {
|
||||
return new ReportItem("Logs", R.string.dev_report_logcat, logs);
|
||||
}
|
||||
|
||||
private ReportItem getDeviceFeatures() {
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.app.Application;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import org.briarproject.bramble.api.FeatureFlags;
|
||||
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
@@ -11,7 +13,6 @@ import org.briarproject.bramble.api.plugin.TorConstants;
|
||||
import org.briarproject.bramble.api.reporting.DevReporter;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.logging.BriefLogFormatter;
|
||||
import org.briarproject.briar.android.logging.CachingLogHandler;
|
||||
import org.briarproject.briar.android.logging.LogDecrypter;
|
||||
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
||||
@@ -22,6 +23,9 @@ import org.json.JSONException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Scanner;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Logger;
|
||||
@@ -39,18 +43,24 @@ import static java.util.Objects.requireNonNull;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.getPersistentLogDir;
|
||||
import static org.briarproject.bramble.util.LogUtils.formatLog;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
import static org.briarproject.briar.android.logging.BriefLogFormatter.formatLog;
|
||||
|
||||
@NotNullByDefault
|
||||
class ReportViewModel extends AndroidViewModel {
|
||||
|
||||
private static final int MAX_PERSISTENT_LOG_LINES = 1000;
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ReportViewModel.class.getName());
|
||||
|
||||
private final CachingLogHandler logHandler;
|
||||
private final LogDecrypter logDecrypter;
|
||||
private final Formatter formatter;
|
||||
private final PersistentLogManager logManager;
|
||||
private final FeatureFlags featureFlags;
|
||||
private final BriarReportCollector collector;
|
||||
private final DevReporter reporter;
|
||||
private final PluginManager pluginManager;
|
||||
@@ -71,12 +81,18 @@ class ReportViewModel extends AndroidViewModel {
|
||||
ReportViewModel(@NonNull Application application,
|
||||
CachingLogHandler logHandler,
|
||||
LogDecrypter logDecrypter,
|
||||
Formatter formatter,
|
||||
PersistentLogManager logManager,
|
||||
FeatureFlags featureFlags,
|
||||
DevReporter reporter,
|
||||
PluginManager pluginManager) {
|
||||
super(application);
|
||||
collector = new BriarReportCollector(application);
|
||||
this.logHandler = logHandler;
|
||||
this.logDecrypter = logDecrypter;
|
||||
this.formatter = formatter;
|
||||
this.logManager = logManager;
|
||||
this.featureFlags = featureFlags;
|
||||
this.reporter = reporter;
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
@@ -86,22 +102,30 @@ class ReportViewModel extends AndroidViewModel {
|
||||
this.initialComment = initialComment;
|
||||
isFeedback = t == null;
|
||||
if (reportData.getValue() == null) new SingleShotAndroidExecutor(() -> {
|
||||
String decryptedLogs;
|
||||
String currentLog;
|
||||
if (isFeedback) {
|
||||
Formatter formatter = new BriefLogFormatter();
|
||||
decryptedLogs =
|
||||
formatLog(formatter, logHandler.getRecentLogRecords());
|
||||
// We're in the main process, so get the log for this process
|
||||
currentLog = formatLog(formatter,
|
||||
logHandler.getRecentLogRecords());
|
||||
} else {
|
||||
decryptedLogs = logDecrypter.decryptLogs(logKey);
|
||||
if (decryptedLogs == null) {
|
||||
// We're in the crash reporter process, so try to load
|
||||
// the encrypted log that was saved by the main process
|
||||
currentLog = logDecrypter.decryptLogs(logKey);
|
||||
if (currentLog == null) {
|
||||
// error decrypting logs, get logs from this process
|
||||
Formatter formatter = new BriefLogFormatter();
|
||||
decryptedLogs = formatLog(formatter,
|
||||
currentLog = formatLog(formatter,
|
||||
logHandler.getRecentLogRecords());
|
||||
}
|
||||
}
|
||||
MultiReportInfo logs = new MultiReportInfo();
|
||||
logs.add("Current", currentLog);
|
||||
if (isFeedback && featureFlags.shouldEnablePersistentLogs()) {
|
||||
// Add persistent logs for the current and previous processes
|
||||
logs.add("Persistent", getPersistentLog(false));
|
||||
logs.add("PersistentOld", getPersistentLog(true));
|
||||
}
|
||||
ReportData data =
|
||||
collector.collectReportData(t, appStartTime, decryptedLogs);
|
||||
collector.collectReportData(t, appStartTime, logs);
|
||||
reportData.postValue(data);
|
||||
}).start();
|
||||
}
|
||||
@@ -226,6 +250,27 @@ class ReportViewModel extends AndroidViewModel {
|
||||
return closeReport;
|
||||
}
|
||||
|
||||
private String getPersistentLog(boolean old) {
|
||||
File logDir = getPersistentLogDir(getApplication());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try {
|
||||
Scanner scanner = logManager.getPersistentLog(logDir, old);
|
||||
LinkedList<String> lines = new LinkedList<>();
|
||||
int numLines = 0;
|
||||
while (scanner.hasNextLine()) {
|
||||
lines.add(scanner.nextLine());
|
||||
// If there are too many lines, return the most recent ones
|
||||
if (numLines == MAX_PERSISTENT_LOG_LINES) lines.pollFirst();
|
||||
else numLines++;
|
||||
}
|
||||
scanner.close();
|
||||
for (String line : lines) sb.append(line).append('\n');
|
||||
} catch (IOException e) {
|
||||
sb.append("Could not recover persistent log: ").append(e);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// Used for a new thread as the Android executor thread may have died
|
||||
private static class SingleShotAndroidExecutor extends Thread {
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.view.View;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.ActivityLaunchers.CreateDocumentAdvanced;
|
||||
import org.briarproject.briar.android.util.ActivityLaunchers.GetImageAdvanced;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -37,6 +38,10 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
private static final String PREF_KEY_DEV = "pref_key_dev";
|
||||
private static final String PREF_KEY_EXPLODE = "pref_key_explode";
|
||||
private static final String PREF_KEY_SHARE_APP = "pref_key_share_app";
|
||||
private static final String PREF_KEY_EXPORT_LOG = "pref_key_export_log";
|
||||
private static final String PREF_EXPORT_OLD_LOG = "pref_key_export_old_log";
|
||||
|
||||
private static final String LOG_EXPORT_FILENAME = "briar-log.txt";
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
@@ -44,10 +49,18 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
private SettingsViewModel viewModel;
|
||||
private AvatarPreference prefAvatar;
|
||||
|
||||
private final ActivityResultLauncher<String> launcher =
|
||||
private final ActivityResultLauncher<String> imageLauncher =
|
||||
registerForActivityResult(new GetImageAdvanced(),
|
||||
this::onImageSelected);
|
||||
|
||||
private final ActivityResultLauncher<String> logLauncher =
|
||||
registerForActivityResult(new CreateDocumentAdvanced(),
|
||||
uri -> onLogFileSelected(false, uri));
|
||||
|
||||
private final ActivityResultLauncher<String> oldLogLauncher =
|
||||
registerForActivityResult(new CreateDocumentAdvanced(),
|
||||
uri -> onLogFileSelected(true, uri));
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
@@ -63,7 +76,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
prefAvatar = requireNonNull(findPreference(PREF_KEY_AVATAR));
|
||||
if (viewModel.shouldEnableProfilePictures()) {
|
||||
prefAvatar.setOnPreferenceClickListener(preference -> {
|
||||
launcher.launch("image/*");
|
||||
imageLauncher.launch("image/*");
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
@@ -77,11 +90,24 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
return true;
|
||||
});
|
||||
|
||||
Preference explode = requireNonNull(findPreference(PREF_KEY_EXPLODE));
|
||||
if (IS_DEBUG_BUILD) {
|
||||
Preference explode =
|
||||
requireNonNull(findPreference(PREF_KEY_EXPLODE));
|
||||
explode.setOnPreferenceClickListener(preference -> {
|
||||
throw new RuntimeException("Boom!");
|
||||
});
|
||||
Preference exportLog =
|
||||
requireNonNull(findPreference(PREF_KEY_EXPORT_LOG));
|
||||
exportLog.setOnPreferenceClickListener(preference -> {
|
||||
logLauncher.launch(LOG_EXPORT_FILENAME);
|
||||
return true;
|
||||
});
|
||||
Preference exportOldLog =
|
||||
requireNonNull(findPreference(PREF_EXPORT_OLD_LOG));
|
||||
exportOldLog.setOnPreferenceClickListener(preference -> {
|
||||
oldLogLauncher.launch(LOG_EXPORT_FILENAME);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
PreferenceGroup dev = requireNonNull(findPreference(PREF_KEY_DEV));
|
||||
dev.setVisible(false);
|
||||
@@ -111,4 +137,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
ConfirmAvatarDialogFragment.TAG);
|
||||
}
|
||||
|
||||
private void onLogFileSelected(boolean old, @Nullable Uri uri) {
|
||||
if (uri != null) viewModel.exportPersistentLog(old, uri);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
||||
@@ -35,8 +36,12 @@ import org.briarproject.briar.api.avatar.AvatarManager;
|
||||
import org.briarproject.briar.api.identity.AuthorInfo;
|
||||
import org.briarproject.briar.api.identity.AuthorManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -50,6 +55,7 @@ import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.getPersistentLogDir;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
@@ -78,6 +84,7 @@ class SettingsViewModel extends DbViewModel implements EventListener {
|
||||
private final ImageCompressor imageCompressor;
|
||||
private final Executor ioExecutor;
|
||||
private final FeatureFlags featureFlags;
|
||||
private final PersistentLogManager logManager;
|
||||
|
||||
final SettingsStore settingsStore;
|
||||
final TorSummaryProvider torSummaryProvider;
|
||||
@@ -108,7 +115,8 @@ class SettingsViewModel extends DbViewModel implements EventListener {
|
||||
LocationUtils locationUtils,
|
||||
CircumventionProvider circumventionProvider,
|
||||
@IoExecutor Executor ioExecutor,
|
||||
FeatureFlags featureFlags) {
|
||||
FeatureFlags featureFlags,
|
||||
PersistentLogManager logManager) {
|
||||
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||
this.settingsManager = settingsManager;
|
||||
this.identityManager = identityManager;
|
||||
@@ -118,6 +126,7 @@ class SettingsViewModel extends DbViewModel implements EventListener {
|
||||
this.authorManager = authorManager;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.featureFlags = featureFlags;
|
||||
this.logManager = logManager;
|
||||
settingsStore = new SettingsStore(settingsManager, dbExecutor,
|
||||
SETTINGS_NAMESPACE);
|
||||
torSummaryProvider = new TorSummaryProvider(getApplication(),
|
||||
@@ -262,4 +271,38 @@ class SettingsViewModel extends DbViewModel implements EventListener {
|
||||
return screenLockTimeout;
|
||||
}
|
||||
|
||||
void exportPersistentLog(boolean old, Uri uri) {
|
||||
// We can use untranslated strings here, as this method is only called
|
||||
// in debug builds
|
||||
ioExecutor.execute(() -> {
|
||||
Application app = getApplication();
|
||||
try {
|
||||
OutputStream os =
|
||||
app.getContentResolver().openOutputStream(uri);
|
||||
if (os == null) throw new IOException();
|
||||
File logDir = getPersistentLogDir(app);
|
||||
Scanner scanner = logManager.getPersistentLog(logDir, old);
|
||||
if (!scanner.hasNextLine()) {
|
||||
scanner.close();
|
||||
androidExecutor.runOnUiThread(() ->
|
||||
Toast.makeText(app, "Log is empty",
|
||||
LENGTH_LONG).show());
|
||||
return;
|
||||
}
|
||||
PrintWriter w = new PrintWriter(os);
|
||||
while (scanner.hasNextLine()) w.println(scanner.nextLine());
|
||||
w.flush();
|
||||
w.close();
|
||||
scanner.close();
|
||||
androidExecutor.runOnUiThread(() ->
|
||||
Toast.makeText(app, "Log exported",
|
||||
LENGTH_LONG).show());
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
androidExecutor.runOnUiThread(() ->
|
||||
Toast.makeText(app, "Failed to export log",
|
||||
LENGTH_LONG).show());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,14 @@
|
||||
android:targetPackage="@string/app_package" />
|
||||
</Preference>
|
||||
|
||||
<Preference
|
||||
android:key="pref_key_export_log"
|
||||
android:title="Export current log to SD card" />
|
||||
|
||||
<Preference
|
||||
android:key="pref_key_export_old_log"
|
||||
android:title="Export previous log to SD card" />
|
||||
|
||||
<Preference
|
||||
android:key="pref_key_explode"
|
||||
android:title="Crash" />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.briar.android.logging;
|
||||
|
||||
import org.briarproject.bramble.logging.BriefLogFormatter;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
@@ -15,8 +16,8 @@ import static java.util.logging.Level.FINE;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.SEVERE;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.formatLog;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.briarproject.briar.android.logging.BriefLogFormatter.formatLog;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class LogEncryptionDecryptionTest extends BrambleMockTestCase {
|
||||
|
||||
@@ -42,7 +42,7 @@ class LoggingTestModule {
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getLogcatFile() {
|
||||
public File getTemporaryLogFile() {
|
||||
return logFile;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user