mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
2 Commits
feature-fl
...
1387-persi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b9955d71f | ||
|
|
719e3c6138 |
@@ -5,12 +5,15 @@ import android.content.Context;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FeatureFlags;
|
||||||
import org.briarproject.bramble.api.account.AccountManager;
|
import org.briarproject.bramble.api.account.AccountManager;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
|
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -23,14 +26,18 @@ import javax.inject.Inject;
|
|||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.logging.Level.INFO;
|
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.IoUtils.deleteFileOrDir;
|
import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
|
import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
|
||||||
|
|
||||||
class AndroidAccountManager extends AccountManagerImpl
|
class AndroidAccountManager extends AccountManagerImpl
|
||||||
implements AccountManager {
|
implements AccountManager {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(AndroidAccountManager.class.getName());
|
getLogger(AndroidAccountManager.class.getName());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directories that shouldn't be deleted when deleting the user's account.
|
* Directories that shouldn't be deleted when deleting the user's account.
|
||||||
@@ -40,13 +47,22 @@ class AndroidAccountManager extends AccountManagerImpl
|
|||||||
|
|
||||||
protected final Context appContext;
|
protected final Context appContext;
|
||||||
private final SharedPreferences prefs;
|
private final SharedPreferences prefs;
|
||||||
|
private final PersistentLogManager logManager;
|
||||||
|
private final FeatureFlags featureFlags;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AndroidAccountManager(DatabaseConfig databaseConfig,
|
AndroidAccountManager(
|
||||||
CryptoComponent crypto, IdentityManager identityManager,
|
DatabaseConfig databaseConfig,
|
||||||
SharedPreferences prefs, Application app) {
|
CryptoComponent crypto,
|
||||||
|
IdentityManager identityManager,
|
||||||
|
SharedPreferences prefs,
|
||||||
|
PersistentLogManager logManager,
|
||||||
|
FeatureFlags featureFlags,
|
||||||
|
Application app) {
|
||||||
super(databaseConfig, crypto, identityManager);
|
super(databaseConfig, crypto, identityManager);
|
||||||
this.prefs = prefs;
|
this.prefs = prefs;
|
||||||
|
this.logManager = logManager;
|
||||||
|
this.featureFlags = featureFlags;
|
||||||
appContext = app.getApplicationContext();
|
appContext = app.getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +90,9 @@ class AndroidAccountManager extends AccountManagerImpl
|
|||||||
LOG.info("Contents of account directory after deleting:");
|
LOG.info("Contents of account directory after deleting:");
|
||||||
logFileOrDir(LOG, INFO, getDataDir());
|
logFileOrDir(LOG, INFO, getDataDir());
|
||||||
}
|
}
|
||||||
|
if (featureFlags.shouldEnablePersistentLogs()) {
|
||||||
|
replacePersistentLogger();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,4 +153,13 @@ class AndroidAccountManager extends AccountManagerImpl
|
|||||||
private void addIfNotNull(Set<File> files, @Nullable File file) {
|
private void addIfNotNull(Set<File> files, @Nullable File file) {
|
||||||
if (file != null) files.add(file);
|
if (file != null) files.add(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void replacePersistentLogger() {
|
||||||
|
File logDir = getPersistentLogDir(appContext);
|
||||||
|
try {
|
||||||
|
logManager.addLogHandler(logDir, getLogger(""));
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,10 +111,14 @@ public class AndroidUtils {
|
|||||||
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
|
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File getLogcatFile(Context ctx) {
|
public static File getTemporaryLogFile(Context ctx) {
|
||||||
return new File(ctx.getFilesDir(), STORED_LOGCAT);
|
return new File(ctx.getFilesDir(), STORED_LOGCAT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static File getPersistentLogDir(Context ctx) {
|
||||||
|
return ctx.getDir("log", MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of supported content types for image attachments.
|
* Returns an array of supported content types for image attachments.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import android.app.Application;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FeatureFlags;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
|
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.lib.legacy.ClassImposteriser;
|
import org.jmock.lib.legacy.ClassImposteriser;
|
||||||
@@ -15,7 +17,9 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
import static junit.framework.Assert.assertFalse;
|
import static junit.framework.Assert.assertFalse;
|
||||||
import static junit.framework.Assert.assertTrue;
|
import static junit.framework.Assert.assertTrue;
|
||||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||||
@@ -27,6 +31,10 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
|
|||||||
context.mock(SharedPreferences.class, "prefs");
|
context.mock(SharedPreferences.class, "prefs");
|
||||||
private final SharedPreferences defaultPrefs =
|
private final SharedPreferences defaultPrefs =
|
||||||
context.mock(SharedPreferences.class, "defaultPrefs");
|
context.mock(SharedPreferences.class, "defaultPrefs");
|
||||||
|
private final PersistentLogManager logManager =
|
||||||
|
context.mock(PersistentLogManager.class);
|
||||||
|
private final FeatureFlags featureFlags =
|
||||||
|
context.mock(FeatureFlags.class);
|
||||||
private final DatabaseConfig databaseConfig =
|
private final DatabaseConfig databaseConfig =
|
||||||
context.mock(DatabaseConfig.class);
|
context.mock(DatabaseConfig.class);
|
||||||
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
@@ -40,6 +48,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
|
|||||||
private final File testDir = getTestDirectory();
|
private final File testDir = getTestDirectory();
|
||||||
private final File keyDir = new File(testDir, "key");
|
private final File keyDir = new File(testDir, "key");
|
||||||
private final File dbDir = new File(testDir, "db");
|
private final File dbDir = new File(testDir, "db");
|
||||||
|
private final File logDir = new File(testDir, "log");
|
||||||
|
|
||||||
private AndroidAccountManager accountManager;
|
private AndroidAccountManager accountManager;
|
||||||
|
|
||||||
@@ -61,7 +70,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(app));
|
will(returnValue(app));
|
||||||
}});
|
}});
|
||||||
accountManager = new AndroidAccountManager(databaseConfig, crypto,
|
accountManager = new AndroidAccountManager(databaseConfig, crypto,
|
||||||
identityManager, prefs, app) {
|
identityManager, prefs, logManager, featureFlags, app) {
|
||||||
@Override
|
@Override
|
||||||
SharedPreferences getDefaultSharedPreferences() {
|
SharedPreferences getDefaultSharedPreferences() {
|
||||||
return defaultPrefs;
|
return defaultPrefs;
|
||||||
@@ -109,10 +118,17 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(cacheDir));
|
will(returnValue(cacheDir));
|
||||||
oneOf(app).getExternalCacheDir();
|
oneOf(app).getExternalCacheDir();
|
||||||
will(returnValue(externalCacheDir));
|
will(returnValue(externalCacheDir));
|
||||||
|
oneOf(featureFlags).shouldEnablePersistentLogs();
|
||||||
|
will(returnValue(true));
|
||||||
|
oneOf(app).getDir("log", MODE_PRIVATE);
|
||||||
|
will(returnValue(logDir));
|
||||||
|
oneOf(logManager).addLogHandler(with(logDir),
|
||||||
|
with(any(Logger.class)));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
assertTrue(dbDir.mkdirs());
|
assertTrue(dbDir.mkdirs());
|
||||||
assertTrue(keyDir.mkdirs());
|
assertTrue(keyDir.mkdirs());
|
||||||
|
assertTrue(logDir.mkdirs());
|
||||||
assertTrue(codeCacheDir.mkdirs());
|
assertTrue(codeCacheDir.mkdirs());
|
||||||
assertTrue(codeCacheFile.createNewFile());
|
assertTrue(codeCacheFile.createNewFile());
|
||||||
assertTrue(libDir.mkdirs());
|
assertTrue(libDir.mkdirs());
|
||||||
@@ -130,6 +146,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
assertFalse(dbDir.exists());
|
assertFalse(dbDir.exists());
|
||||||
assertFalse(keyDir.exists());
|
assertFalse(keyDir.exists());
|
||||||
|
assertFalse(logDir.exists());
|
||||||
assertTrue(codeCacheDir.exists());
|
assertTrue(codeCacheDir.exists());
|
||||||
assertTrue(codeCacheFile.exists());
|
assertTrue(codeCacheFile.exists());
|
||||||
assertTrue(libDir.exists());
|
assertTrue(libDir.exists());
|
||||||
|
|||||||
@@ -10,4 +10,6 @@ public interface FeatureFlags {
|
|||||||
boolean shouldEnableProfilePictures();
|
boolean shouldEnableProfilePictures();
|
||||||
|
|
||||||
boolean shouldEnableDisappearingMessages();
|
boolean shouldEnableDisappearingMessages();
|
||||||
|
|
||||||
|
boolean shouldEnablePersistentLogs();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
package org.briarproject.bramble.api;
|
package org.briarproject.bramble.api;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.fromHexString;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.toHexString;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
public abstract class StringMap extends Hashtable<String, String> {
|
public abstract class StringMap extends Hashtable<String, String> {
|
||||||
|
|
||||||
protected StringMap(Map<String, String> m) {
|
protected StringMap(Map<String, String> m) {
|
||||||
@@ -52,4 +60,19 @@ public abstract class StringMap extends Hashtable<String, String> {
|
|||||||
public void putLong(String key, long value) {
|
public void putLong(String key, long value) {
|
||||||
put(key, String.valueOf(value));
|
put(key, String.valueOf(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public byte[] getBytes(String key) {
|
||||||
|
String s = get(key);
|
||||||
|
if (s == null) return null;
|
||||||
|
try {
|
||||||
|
return fromHexString(s);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putBytes(String key, byte[] value) {
|
||||||
|
put(key, toHexString(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package org.briarproject.bramble.api.logging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.logging.Handler;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface PersistentLogManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The namespace of the (@link Settings) where the log key is stored.
|
||||||
|
*/
|
||||||
|
String LOG_SETTINGS_NAMESPACE = "log";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Settings} key under which the log key is stored.
|
||||||
|
*/
|
||||||
|
String LOG_KEY_KEY = "logKey";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a persistent log handler that stores its logs in
|
||||||
|
* the given directory.
|
||||||
|
*/
|
||||||
|
Handler createLogHandler(File dir) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a persistent log handler that stores its logs in the given
|
||||||
|
* directory and adds the handler to the given logger, replacing any
|
||||||
|
* existing persistent log handler.
|
||||||
|
*/
|
||||||
|
void addLogHandler(File dir, Logger logger) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Scanner} for reading the persistent log entries stored
|
||||||
|
* in the given directory.
|
||||||
|
*
|
||||||
|
* @param old True if the previous session's log should be loaded, or false
|
||||||
|
* if the current session's log should be loaded
|
||||||
|
*/
|
||||||
|
Scanner getPersistentLog(File dir, boolean old) throws IOException;
|
||||||
|
}
|
||||||
@@ -8,11 +8,24 @@ import java.io.File;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface DevConfig {
|
public interface DevConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the public key for encrypting feedback and crash reports.
|
||||||
|
*/
|
||||||
PublicKey getDevPublicKey();
|
PublicKey getDevPublicKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the onion address for submitting feedback and crash reports.
|
||||||
|
*/
|
||||||
String getDevOnionAddress();
|
String getDevOnionAddress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the directory for storing unsent feedback and crash reports.
|
||||||
|
*/
|
||||||
File getReportDir();
|
File getReportDir();
|
||||||
|
|
||||||
File getLogcatFile();
|
/**
|
||||||
|
* Returns the temporary file for passing the encrypted app log from the
|
||||||
|
* main process to the crash reporter process.
|
||||||
|
*/
|
||||||
|
File getTemporaryLogFile();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package org.briarproject.bramble.util;
|
package org.briarproject.bramble.util;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.logging.Formatter;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static java.util.logging.Level.FINE;
|
import static java.util.logging.Level.FINE;
|
||||||
@@ -57,4 +60,13 @@ public class LogUtils {
|
|||||||
String type) {
|
String type) {
|
||||||
logger.log(level, type + " " + f.getAbsolutePath() + " " + f.length());
|
logger.log(level, type + " " + f.getAbsolutePath() + " " + f.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String formatLog(Formatter formatter,
|
||||||
|
Collection<LogRecord> logRecords) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (LogRecord record : logRecords) {
|
||||||
|
sb.append(formatter.format(record)).append('\n');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import org.briarproject.bramble.identity.IdentityModule;
|
|||||||
import org.briarproject.bramble.io.IoModule;
|
import org.briarproject.bramble.io.IoModule;
|
||||||
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
|
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
|
||||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||||
|
import org.briarproject.bramble.logging.LoggingModule;
|
||||||
import org.briarproject.bramble.mailbox.MailboxModule;
|
import org.briarproject.bramble.mailbox.MailboxModule;
|
||||||
import org.briarproject.bramble.plugin.PluginModule;
|
import org.briarproject.bramble.plugin.PluginModule;
|
||||||
import org.briarproject.bramble.properties.PropertiesModule;
|
import org.briarproject.bramble.properties.PropertiesModule;
|
||||||
@@ -44,6 +45,7 @@ import dagger.Module;
|
|||||||
IoModule.class,
|
IoModule.class,
|
||||||
KeyAgreementModule.class,
|
KeyAgreementModule.class,
|
||||||
LifecycleModule.class,
|
LifecycleModule.class,
|
||||||
|
LoggingModule.class,
|
||||||
MailboxModule.class,
|
MailboxModule.class,
|
||||||
PluginModule.class,
|
PluginModule.class,
|
||||||
PropertiesModule.class,
|
PropertiesModule.class,
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package org.briarproject.briar.android.logging;
|
package org.briarproject.bramble.logging;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.logging.Formatter;
|
import java.util.logging.Formatter;
|
||||||
@@ -18,16 +17,6 @@ import static java.util.Locale.US;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class BriefLogFormatter extends Formatter {
|
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 Object lock = new Object();
|
||||||
private final DateFormat dateFormat; // Locking: lock
|
private final DateFormat dateFormat; // Locking: lock
|
||||||
private final Date date; // Locking: lock
|
private final Date date; // Locking: lock
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.briarproject.bramble.logging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.logging.Formatter;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
import java.util.logging.StreamHandler;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
|
class FlushingStreamHandler extends StreamHandler {
|
||||||
|
|
||||||
|
private static final int FLUSH_DELAY_MS = 5_000;
|
||||||
|
|
||||||
|
private final TaskScheduler scheduler;
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final AtomicBoolean flushScheduled = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
FlushingStreamHandler(TaskScheduler scheduler,
|
||||||
|
Executor ioExecutor, OutputStream out, Formatter formatter) {
|
||||||
|
super(out, formatter);
|
||||||
|
this.scheduler = scheduler;
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void publish(LogRecord record) {
|
||||||
|
super.publish(record);
|
||||||
|
if (!flushScheduled.getAndSet(true)) {
|
||||||
|
scheduler.schedule(this::scheduledFlush, ioExecutor,
|
||||||
|
FLUSH_DELAY_MS, MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private void scheduledFlush() {
|
||||||
|
flushScheduled.set(false);
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.briarproject.bramble.logging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||||
|
|
||||||
|
import java.util.logging.Formatter;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class LoggingModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
Formatter provideFormatter() {
|
||||||
|
return new BriefLogFormatter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
PersistentLogManager providePersistentLogManager(
|
||||||
|
LifecycleManager lifecycleManager,
|
||||||
|
PersistentLogManagerImpl persistentLogManager) {
|
||||||
|
lifecycleManager.registerOpenDatabaseHook(persistentLogManager);
|
||||||
|
return persistentLogManager;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package org.briarproject.bramble.logging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
|
||||||
|
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
|
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.logging.Formatter;
|
||||||
|
import java.util.logging.Handler;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.logging.StreamHandler;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class PersistentLogManagerImpl implements PersistentLogManager,
|
||||||
|
OpenDatabaseHook {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(PersistentLogManagerImpl.class.getName());
|
||||||
|
|
||||||
|
private static final String LOG_FILE = "briar.log";
|
||||||
|
private static final String OLD_LOG_FILE = "briar.log.old";
|
||||||
|
|
||||||
|
private final TaskScheduler scheduler;
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final ShutdownManager shutdownManager;
|
||||||
|
private final DatabaseComponent db;
|
||||||
|
private final StreamReaderFactory streamReaderFactory;
|
||||||
|
private final StreamWriterFactory streamWriterFactory;
|
||||||
|
private final Formatter formatter;
|
||||||
|
private final SecretKey logKey;
|
||||||
|
private final AtomicReference<Integer> shutdownHookHandle =
|
||||||
|
new AtomicReference<>();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private volatile SecretKey oldLogKey = null;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
PersistentLogManagerImpl(
|
||||||
|
TaskScheduler scheduler,
|
||||||
|
@IoExecutor Executor ioExecutor,
|
||||||
|
ShutdownManager shutdownManager,
|
||||||
|
DatabaseComponent db,
|
||||||
|
StreamReaderFactory streamReaderFactory,
|
||||||
|
StreamWriterFactory streamWriterFactory,
|
||||||
|
Formatter formatter,
|
||||||
|
CryptoComponent crypto) {
|
||||||
|
this.scheduler = scheduler;
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.shutdownManager = shutdownManager;
|
||||||
|
this.db = db;
|
||||||
|
this.streamReaderFactory = streamReaderFactory;
|
||||||
|
this.streamWriterFactory = streamWriterFactory;
|
||||||
|
this.formatter = formatter;
|
||||||
|
logKey = crypto.generateSecretKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDatabaseOpened(Transaction txn) throws DbException {
|
||||||
|
Settings s = db.getSettings(txn, LOG_SETTINGS_NAMESPACE);
|
||||||
|
// Load the old log key, if any
|
||||||
|
byte[] oldKeyBytes = s.getBytes(LOG_KEY_KEY);
|
||||||
|
if (oldKeyBytes != null && oldKeyBytes.length == SecretKey.LENGTH) {
|
||||||
|
LOG.info("Loaded old log key");
|
||||||
|
oldLogKey = new SecretKey(oldKeyBytes);
|
||||||
|
}
|
||||||
|
// Store the current log key
|
||||||
|
s.putBytes(LOG_KEY_KEY, logKey.getBytes());
|
||||||
|
db.mergeSettings(txn, s, LOG_SETTINGS_NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Handler createLogHandler(File dir) throws IOException {
|
||||||
|
File logFile = new File(dir, LOG_FILE);
|
||||||
|
File oldLogFile = new File(dir, OLD_LOG_FILE);
|
||||||
|
if (oldLogFile.exists() && !oldLogFile.delete())
|
||||||
|
LOG.warning("Failed to delete old log file");
|
||||||
|
if (logFile.exists() && !logFile.renameTo(oldLogFile))
|
||||||
|
LOG.warning("Failed to rename log file");
|
||||||
|
try {
|
||||||
|
OutputStream out = new FileOutputStream(logFile);
|
||||||
|
StreamWriter writer =
|
||||||
|
streamWriterFactory.createLogStreamWriter(out, logKey);
|
||||||
|
StreamHandler handler = new FlushingStreamHandler(scheduler,
|
||||||
|
ioExecutor, writer.getOutputStream(), formatter);
|
||||||
|
// Flush the log and terminate the stream at shutdown
|
||||||
|
Runnable shutdownHook = () -> {
|
||||||
|
LOG.info("Shutting down");
|
||||||
|
handler.flush();
|
||||||
|
try {
|
||||||
|
writer.sendEndOfStream();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
int handle = shutdownManager.addShutdownHook(shutdownHook);
|
||||||
|
// If a previous handler registered a shutdown hook, remove it
|
||||||
|
Integer oldHandle = shutdownHookHandle.getAndSet(handle);
|
||||||
|
if (oldHandle != null) {
|
||||||
|
shutdownManager.removeShutdownHook(oldHandle);
|
||||||
|
}
|
||||||
|
return handler;
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addLogHandler(File dir, Logger logger) throws IOException {
|
||||||
|
for (Handler h : logger.getHandlers()) {
|
||||||
|
if (h instanceof FlushingStreamHandler) logger.removeHandler(h);
|
||||||
|
}
|
||||||
|
logger.addHandler(createLogHandler(dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Scanner getPersistentLog(File dir, boolean old)
|
||||||
|
throws IOException {
|
||||||
|
if (old) {
|
||||||
|
SecretKey oldLogKey = this.oldLogKey;
|
||||||
|
if (oldLogKey == null) {
|
||||||
|
LOG.info("Old log key has not been loaded");
|
||||||
|
return emptyScanner();
|
||||||
|
}
|
||||||
|
return getPersistentLog(new File(dir, OLD_LOG_FILE), oldLogKey);
|
||||||
|
} else {
|
||||||
|
return getPersistentLog(new File(dir, LOG_FILE), logKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Scanner getPersistentLog(File logFile, SecretKey key)
|
||||||
|
throws IOException {
|
||||||
|
if (logFile.exists()) {
|
||||||
|
LOG.info("Reading log file");
|
||||||
|
InputStream in = new FileInputStream(logFile);
|
||||||
|
return new Scanner(streamReaderFactory.createLogStreamReader(in,
|
||||||
|
key));
|
||||||
|
} else {
|
||||||
|
LOG.info("Log file does not exist");
|
||||||
|
return emptyScanner();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Scanner emptyScanner() {
|
||||||
|
return new Scanner(new ByteArrayInputStream(new byte[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,11 @@ public class TestFeatureFlagModule {
|
|||||||
public boolean shouldEnableDisappearingMessages() {
|
public boolean shouldEnableDisappearingMessages() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnablePersistentLogs() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package org.briarproject.bramble.account;
|
|||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FeatureFlags;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
|
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.Localizer;
|
import org.briarproject.briar.android.Localizer;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
@@ -15,10 +17,16 @@ import javax.inject.Inject;
|
|||||||
class BriarAccountManager extends AndroidAccountManager {
|
class BriarAccountManager extends AndroidAccountManager {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BriarAccountManager(DatabaseConfig databaseConfig, CryptoComponent crypto,
|
BriarAccountManager(
|
||||||
IdentityManager identityManager, SharedPreferences prefs,
|
DatabaseConfig databaseConfig,
|
||||||
|
CryptoComponent crypto,
|
||||||
|
IdentityManager identityManager,
|
||||||
|
SharedPreferences prefs,
|
||||||
|
PersistentLogManager logManager,
|
||||||
|
FeatureFlags featureFlags,
|
||||||
Application app) {
|
Application app) {
|
||||||
super(databaseConfig, crypto, identityManager, prefs, app);
|
super(databaseConfig, crypto, identityManager, prefs, logManager,
|
||||||
|
featureFlags, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
|||||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
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.plugin.PluginManager;
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
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 org.briarproject.briar.api.test.TestDataCreator;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Formatter;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
@@ -204,6 +206,10 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
AutoDeleteManager autoDeleteManager();
|
AutoDeleteManager autoDeleteManager();
|
||||||
|
|
||||||
|
PersistentLogManager persistentLogManager();
|
||||||
|
|
||||||
|
Formatter formatter();
|
||||||
|
|
||||||
void inject(SignInReminderReceiver briarService);
|
void inject(SignInReminderReceiver briarService);
|
||||||
|
|
||||||
void inject(BriarService briarService);
|
void inject(BriarService briarService);
|
||||||
|
|||||||
@@ -245,8 +245,9 @@ public class AppModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File getLogcatFile() {
|
public File getTemporaryLogFile() {
|
||||||
return AndroidUtils.getLogcatFile(app.getApplicationContext());
|
return AndroidUtils
|
||||||
|
.getTemporaryLogFile(app.getApplicationContext());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return devConfig;
|
return devConfig;
|
||||||
@@ -337,6 +338,11 @@ public class AppModule {
|
|||||||
public boolean shouldEnableDisappearingMessages() {
|
public boolean shouldEnableDisappearingMessages() {
|
||||||
return true;
|
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.BrambleAndroidEagerSingletons;
|
||||||
import org.briarproject.bramble.BrambleAppComponent;
|
import org.briarproject.bramble.BrambleAppComponent;
|
||||||
import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
||||||
|
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.logging.CachingLogHandler;
|
import org.briarproject.briar.android.logging.CachingLogHandler;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.Thread.UncaughtExceptionHandler;
|
import java.lang.Thread.UncaughtExceptionHandler;
|
||||||
import java.util.logging.Handler;
|
import java.util.logging.Handler;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -31,7 +34,10 @@ import androidx.annotation.NonNull;
|
|||||||
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
||||||
import static java.util.logging.Level.FINE;
|
import static java.util.logging.Level.FINE;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
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;
|
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||||
|
|
||||||
public class BriarApplicationImpl extends Application
|
public class BriarApplicationImpl extends Application
|
||||||
@@ -81,6 +87,17 @@ public class BriarApplicationImpl extends Application
|
|||||||
rootLogger.addHandler(logHandler);
|
rootLogger.addHandler(logHandler);
|
||||||
rootLogger.setLevel(IS_DEBUG_BUILD ? FINE : INFO);
|
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");
|
LOG.info("Created");
|
||||||
|
|
||||||
EmojiManager.install(new GoogleEmojiProvider());
|
EmojiManager.install(new GoogleEmojiProvider());
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import androidx.annotation.Nullable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface LogDecrypter {
|
public interface LogDecrypter {
|
||||||
/**
|
/**
|
||||||
* Returns decrypted log records from {@link AndroidUtils#getLogcatFile}
|
* Returns decrypted log records from
|
||||||
* or null if there was an error reading the logs.
|
* {@link AndroidUtils#getTemporaryLogFile} or null if there was an error
|
||||||
|
* reading the logs.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
String decryptLogs(@Nullable byte[] logKey);
|
String decryptLogs(@Nullable byte[] logKey);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class LogDecrypterImpl implements LogDecrypter {
|
|||||||
public String decryptLogs(@Nullable byte[] logKey) {
|
public String decryptLogs(@Nullable byte[] logKey) {
|
||||||
if (logKey == null) return null;
|
if (logKey == null) return null;
|
||||||
SecretKey key = new SecretKey(logKey);
|
SecretKey key = new SecretKey(logKey);
|
||||||
File logFile = devConfig.getLogcatFile();
|
File logFile = devConfig.getTemporaryLogFile();
|
||||||
try (InputStream in = new FileInputStream(logFile)) {
|
try (InputStream in = new FileInputStream(logFile)) {
|
||||||
InputStream reader =
|
InputStream reader =
|
||||||
streamReaderFactory.createLogStreamReader(in, key);
|
streamReaderFactory.createLogStreamReader(in, key);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import androidx.annotation.Nullable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface LogEncrypter {
|
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.
|
* and returns the encryption key if everything went fine.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -33,16 +33,19 @@ class LogEncrypterImpl implements LogEncrypter {
|
|||||||
|
|
||||||
private final DevConfig devConfig;
|
private final DevConfig devConfig;
|
||||||
private final CachingLogHandler logHandler;
|
private final CachingLogHandler logHandler;
|
||||||
|
private final Formatter formatter;
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
private final StreamWriterFactory streamWriterFactory;
|
private final StreamWriterFactory streamWriterFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
LogEncrypterImpl(DevConfig devConfig,
|
LogEncrypterImpl(DevConfig devConfig,
|
||||||
CachingLogHandler logHandler,
|
CachingLogHandler logHandler,
|
||||||
|
Formatter formatter,
|
||||||
CryptoComponent crypto,
|
CryptoComponent crypto,
|
||||||
StreamWriterFactory streamWriterFactory) {
|
StreamWriterFactory streamWriterFactory) {
|
||||||
this.devConfig = devConfig;
|
this.devConfig = devConfig;
|
||||||
this.logHandler = logHandler;
|
this.logHandler = logHandler;
|
||||||
|
this.formatter = formatter;
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.streamWriterFactory = streamWriterFactory;
|
this.streamWriterFactory = streamWriterFactory;
|
||||||
}
|
}
|
||||||
@@ -51,7 +54,7 @@ class LogEncrypterImpl implements LogEncrypter {
|
|||||||
@Override
|
@Override
|
||||||
public byte[] encryptLogs() {
|
public byte[] encryptLogs() {
|
||||||
SecretKey logKey = crypto.generateSecretKey();
|
SecretKey logKey = crypto.generateSecretKey();
|
||||||
File logFile = devConfig.getLogcatFile();
|
File logFile = devConfig.getTemporaryLogFile();
|
||||||
try (OutputStream out = new FileOutputStream(logFile)) {
|
try (OutputStream out = new FileOutputStream(logFile)) {
|
||||||
StreamWriter streamWriter =
|
StreamWriter streamWriter =
|
||||||
streamWriterFactory.createLogStreamWriter(out, logKey);
|
streamWriterFactory.createLogStreamWriter(out, logKey);
|
||||||
@@ -67,10 +70,8 @@ class LogEncrypterImpl implements LogEncrypter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void writeLogString(Writer writer) throws IOException {
|
private void writeLogString(Writer writer) throws IOException {
|
||||||
Formatter formatter = new BriefLogFormatter();
|
|
||||||
for (LogRecord record : logHandler.getRecentLogRecords()) {
|
for (LogRecord record : logHandler.getRecentLogRecords()) {
|
||||||
String formatted = formatter.format(record);
|
writer.append(formatter.format(record)).append('\n');
|
||||||
writer.append(formatted).append('\n');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.briar.BuildConfig;
|
import org.briarproject.briar.BuildConfig;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
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.ReportItem;
|
||||||
import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo;
|
import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo;
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ class BriarReportCollector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ReportData collectReportData(@Nullable Throwable t, long appStartTime,
|
ReportData collectReportData(@Nullable Throwable t, long appStartTime,
|
||||||
String logs) {
|
ReportInfo logs) {
|
||||||
ReportData reportData = new ReportData()
|
ReportData reportData = new ReportData()
|
||||||
.add(getBasicInfo(t))
|
.add(getBasicInfo(t))
|
||||||
.add(getDeviceInfo());
|
.add(getDeviceInfo());
|
||||||
@@ -82,7 +83,7 @@ class BriarReportCollector {
|
|||||||
.add(getStorage())
|
.add(getStorage())
|
||||||
.add(getConnectivity())
|
.add(getConnectivity())
|
||||||
.add(getBuildConfig())
|
.add(getBuildConfig())
|
||||||
.add(getLogcat(logs))
|
.add(getLogs(logs))
|
||||||
.add(getDeviceFeatures());
|
.add(getDeviceFeatures());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,8 +310,8 @@ class BriarReportCollector {
|
|||||||
buildConfig);
|
buildConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReportItem getLogcat(String logs) {
|
private ReportItem getLogs(ReportInfo logs) {
|
||||||
return new ReportItem("Logcat", R.string.dev_report_logcat, logs);
|
return new ReportItem("Logs", R.string.dev_report_logcat, logs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReportItem getDeviceFeatures() {
|
private ReportItem getDeviceFeatures() {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import android.app.Application;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
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.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.Plugin;
|
import org.briarproject.bramble.api.plugin.Plugin;
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
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.api.reporting.DevReporter;
|
||||||
import org.briarproject.bramble.util.AndroidUtils;
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
import org.briarproject.briar.R;
|
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.CachingLogHandler;
|
||||||
import org.briarproject.briar.android.logging.LogDecrypter;
|
import org.briarproject.briar.android.logging.LogDecrypter;
|
||||||
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
||||||
@@ -22,6 +23,9 @@ import org.json.JSONException;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Scanner;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.logging.Formatter;
|
import java.util.logging.Formatter;
|
||||||
import java.util.logging.Logger;
|
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.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
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.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
import static org.briarproject.briar.android.logging.BriefLogFormatter.formatLog;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ReportViewModel extends AndroidViewModel {
|
class ReportViewModel extends AndroidViewModel {
|
||||||
|
|
||||||
|
private static final int MAX_PERSISTENT_LOG_LINES = 1000;
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(ReportViewModel.class.getName());
|
getLogger(ReportViewModel.class.getName());
|
||||||
|
|
||||||
private final CachingLogHandler logHandler;
|
private final CachingLogHandler logHandler;
|
||||||
private final LogDecrypter logDecrypter;
|
private final LogDecrypter logDecrypter;
|
||||||
|
private final Formatter formatter;
|
||||||
|
private final PersistentLogManager logManager;
|
||||||
|
private final FeatureFlags featureFlags;
|
||||||
private final BriarReportCollector collector;
|
private final BriarReportCollector collector;
|
||||||
private final DevReporter reporter;
|
private final DevReporter reporter;
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
@@ -71,12 +81,18 @@ class ReportViewModel extends AndroidViewModel {
|
|||||||
ReportViewModel(@NonNull Application application,
|
ReportViewModel(@NonNull Application application,
|
||||||
CachingLogHandler logHandler,
|
CachingLogHandler logHandler,
|
||||||
LogDecrypter logDecrypter,
|
LogDecrypter logDecrypter,
|
||||||
|
Formatter formatter,
|
||||||
|
PersistentLogManager logManager,
|
||||||
|
FeatureFlags featureFlags,
|
||||||
DevReporter reporter,
|
DevReporter reporter,
|
||||||
PluginManager pluginManager) {
|
PluginManager pluginManager) {
|
||||||
super(application);
|
super(application);
|
||||||
collector = new BriarReportCollector(application);
|
collector = new BriarReportCollector(application);
|
||||||
this.logHandler = logHandler;
|
this.logHandler = logHandler;
|
||||||
this.logDecrypter = logDecrypter;
|
this.logDecrypter = logDecrypter;
|
||||||
|
this.formatter = formatter;
|
||||||
|
this.logManager = logManager;
|
||||||
|
this.featureFlags = featureFlags;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
}
|
}
|
||||||
@@ -86,22 +102,30 @@ class ReportViewModel extends AndroidViewModel {
|
|||||||
this.initialComment = initialComment;
|
this.initialComment = initialComment;
|
||||||
isFeedback = t == null;
|
isFeedback = t == null;
|
||||||
if (reportData.getValue() == null) new SingleShotAndroidExecutor(() -> {
|
if (reportData.getValue() == null) new SingleShotAndroidExecutor(() -> {
|
||||||
String decryptedLogs;
|
String currentLog;
|
||||||
if (isFeedback) {
|
if (isFeedback) {
|
||||||
Formatter formatter = new BriefLogFormatter();
|
// We're in the main process, so get the log for this process
|
||||||
decryptedLogs =
|
currentLog = formatLog(formatter,
|
||||||
formatLog(formatter, logHandler.getRecentLogRecords());
|
logHandler.getRecentLogRecords());
|
||||||
} else {
|
} else {
|
||||||
decryptedLogs = logDecrypter.decryptLogs(logKey);
|
// We're in the crash reporter process, so try to load
|
||||||
if (decryptedLogs == null) {
|
// 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
|
// error decrypting logs, get logs from this process
|
||||||
Formatter formatter = new BriefLogFormatter();
|
currentLog = formatLog(formatter,
|
||||||
decryptedLogs = formatLog(formatter,
|
|
||||||
logHandler.getRecentLogRecords());
|
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 =
|
ReportData data =
|
||||||
collector.collectReportData(t, appStartTime, decryptedLogs);
|
collector.collectReportData(t, appStartTime, logs);
|
||||||
reportData.postValue(data);
|
reportData.postValue(data);
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
@@ -226,6 +250,27 @@ class ReportViewModel extends AndroidViewModel {
|
|||||||
return closeReport;
|
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
|
// Used for a new thread as the Android executor thread may have died
|
||||||
private static class SingleShotAndroidExecutor extends Thread {
|
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.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.util.ActivityLaunchers.CreateDocumentAdvanced;
|
||||||
import org.briarproject.briar.android.util.ActivityLaunchers.GetImageAdvanced;
|
import org.briarproject.briar.android.util.ActivityLaunchers.GetImageAdvanced;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -21,6 +22,7 @@ import androidx.preference.Preference;
|
|||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
import androidx.preference.PreferenceGroup;
|
import androidx.preference.PreferenceGroup;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
||||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||||
@@ -37,6 +39,10 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
private static final String PREF_KEY_DEV = "pref_key_dev";
|
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_EXPLODE = "pref_key_explode";
|
||||||
private static final String PREF_KEY_SHARE_APP = "pref_key_share_app";
|
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
|
@Inject
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
@@ -44,10 +50,18 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
private SettingsViewModel viewModel;
|
private SettingsViewModel viewModel;
|
||||||
private AvatarPreference prefAvatar;
|
private AvatarPreference prefAvatar;
|
||||||
|
|
||||||
private final ActivityResultLauncher<String> launcher =
|
private final ActivityResultLauncher<String> imageLauncher =
|
||||||
registerForActivityResult(new GetImageAdvanced(),
|
registerForActivityResult(new GetImageAdvanced(),
|
||||||
this::onImageSelected);
|
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
|
@Override
|
||||||
public void onAttach(@NonNull Context context) {
|
public void onAttach(@NonNull Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
@@ -63,7 +77,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
prefAvatar = requireNonNull(findPreference(PREF_KEY_AVATAR));
|
prefAvatar = requireNonNull(findPreference(PREF_KEY_AVATAR));
|
||||||
if (viewModel.shouldEnableProfilePictures()) {
|
if (viewModel.shouldEnableProfilePictures()) {
|
||||||
prefAvatar.setOnPreferenceClickListener(preference -> {
|
prefAvatar.setOnPreferenceClickListener(preference -> {
|
||||||
launcher.launch("image/*");
|
imageLauncher.launch("image/*");
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -77,11 +91,32 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
Preference explode = requireNonNull(findPreference(PREF_KEY_EXPLODE));
|
|
||||||
if (IS_DEBUG_BUILD) {
|
if (IS_DEBUG_BUILD) {
|
||||||
|
Preference explode =
|
||||||
|
requireNonNull(findPreference(PREF_KEY_EXPLODE));
|
||||||
explode.setOnPreferenceClickListener(preference -> {
|
explode.setOnPreferenceClickListener(preference -> {
|
||||||
throw new RuntimeException("Boom!");
|
throw new RuntimeException("Boom!");
|
||||||
});
|
});
|
||||||
|
Preference exportLog =
|
||||||
|
requireNonNull(findPreference(PREF_KEY_EXPORT_LOG));
|
||||||
|
if (SDK_INT >= 19) {
|
||||||
|
exportLog.setOnPreferenceClickListener(preference -> {
|
||||||
|
logLauncher.launch(LOG_EXPORT_FILENAME);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
exportLog.setVisible(false);
|
||||||
|
}
|
||||||
|
Preference exportOldLog =
|
||||||
|
requireNonNull(findPreference(PREF_EXPORT_OLD_LOG));
|
||||||
|
if (SDK_INT >= 19) {
|
||||||
|
exportOldLog.setOnPreferenceClickListener(preference -> {
|
||||||
|
oldLogLauncher.launch(LOG_EXPORT_FILENAME);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
exportOldLog.setVisible(false);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
PreferenceGroup dev = requireNonNull(findPreference(PREF_KEY_DEV));
|
PreferenceGroup dev = requireNonNull(findPreference(PREF_KEY_DEV));
|
||||||
dev.setVisible(false);
|
dev.setVisible(false);
|
||||||
@@ -111,4 +146,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
ConfirmAvatarDialogFragment.TAG);
|
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.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
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.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
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.AuthorInfo;
|
||||||
import org.briarproject.briar.api.identity.AuthorManager;
|
import org.briarproject.briar.api.identity.AuthorManager;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.Scanner;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
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.Arrays.asList;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
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.AndroidUtils.getSupportedImageContentTypes;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
@@ -78,6 +84,7 @@ class SettingsViewModel extends DbViewModel implements EventListener {
|
|||||||
private final ImageCompressor imageCompressor;
|
private final ImageCompressor imageCompressor;
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
private final FeatureFlags featureFlags;
|
private final FeatureFlags featureFlags;
|
||||||
|
private final PersistentLogManager logManager;
|
||||||
|
|
||||||
final SettingsStore settingsStore;
|
final SettingsStore settingsStore;
|
||||||
final TorSummaryProvider torSummaryProvider;
|
final TorSummaryProvider torSummaryProvider;
|
||||||
@@ -108,7 +115,8 @@ class SettingsViewModel extends DbViewModel implements EventListener {
|
|||||||
LocationUtils locationUtils,
|
LocationUtils locationUtils,
|
||||||
CircumventionProvider circumventionProvider,
|
CircumventionProvider circumventionProvider,
|
||||||
@IoExecutor Executor ioExecutor,
|
@IoExecutor Executor ioExecutor,
|
||||||
FeatureFlags featureFlags) {
|
FeatureFlags featureFlags,
|
||||||
|
PersistentLogManager logManager) {
|
||||||
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||||
this.settingsManager = settingsManager;
|
this.settingsManager = settingsManager;
|
||||||
this.identityManager = identityManager;
|
this.identityManager = identityManager;
|
||||||
@@ -118,6 +126,7 @@ class SettingsViewModel extends DbViewModel implements EventListener {
|
|||||||
this.authorManager = authorManager;
|
this.authorManager = authorManager;
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.featureFlags = featureFlags;
|
this.featureFlags = featureFlags;
|
||||||
|
this.logManager = logManager;
|
||||||
settingsStore = new SettingsStore(settingsManager, dbExecutor,
|
settingsStore = new SettingsStore(settingsManager, dbExecutor,
|
||||||
SETTINGS_NAMESPACE);
|
SETTINGS_NAMESPACE);
|
||||||
torSummaryProvider = new TorSummaryProvider(getApplication(),
|
torSummaryProvider = new TorSummaryProvider(getApplication(),
|
||||||
@@ -262,4 +271,38 @@ class SettingsViewModel extends DbViewModel implements EventListener {
|
|||||||
return screenLockTimeout;
|
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" />
|
android:targetPackage="@string/app_package" />
|
||||||
</Preference>
|
</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
|
<Preference
|
||||||
android:key="pref_key_explode"
|
android:key="pref_key_explode"
|
||||||
android:title="Crash" />
|
android:title="Crash" />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.briar.android.logging;
|
package org.briarproject.briar.android.logging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.logging.BriefLogFormatter;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.Test;
|
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.INFO;
|
||||||
import static java.util.logging.Level.SEVERE;
|
import static java.util.logging.Level.SEVERE;
|
||||||
import static java.util.logging.Level.WARNING;
|
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.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.briarproject.briar.android.logging.BriefLogFormatter.formatLog;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public class LogEncryptionDecryptionTest extends BrambleMockTestCase {
|
public class LogEncryptionDecryptionTest extends BrambleMockTestCase {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class LoggingTestModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File getLogcatFile() {
|
public File getTemporaryLogFile() {
|
||||||
return logFile;
|
return logFile;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -107,5 +107,6 @@ internal class HeadlessModule(private val appDir: File) {
|
|||||||
override fun shouldEnableImageAttachments() = false
|
override fun shouldEnableImageAttachments() = false
|
||||||
override fun shouldEnableProfilePictures() = false
|
override fun shouldEnableProfilePictures() = false
|
||||||
override fun shouldEnableDisappearingMessages() = false
|
override fun shouldEnableDisappearingMessages() = false
|
||||||
|
override fun shouldEnablePersistentLogs() = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user