mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
14 Commits
offline-te
...
1387-persi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ea4463cbc | ||
|
|
a83682d4b6 | ||
|
|
b1ec344bdb | ||
|
|
821327d62e | ||
|
|
cda722a8b2 | ||
|
|
ca5c18ece3 | ||
|
|
81ed5978d6 | ||
|
|
a63619ab47 | ||
|
|
2d88819e80 | ||
|
|
fef19c1329 | ||
|
|
7ce91066f5 | ||
|
|
dfb581ef12 | ||
|
|
d9b4c013bb | ||
|
|
61407c3e06 |
@@ -9,8 +9,10 @@ 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;
|
||||||
@@ -20,17 +22,21 @@ import javax.annotation.Nullable;
|
|||||||
import javax.annotation.concurrent.GuardedBy;
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
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.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 +46,16 @@ class AndroidAccountManager extends AccountManagerImpl
|
|||||||
|
|
||||||
protected final Context appContext;
|
protected final Context appContext;
|
||||||
private final SharedPreferences prefs;
|
private final SharedPreferences prefs;
|
||||||
|
private final PersistentLogManager logManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AndroidAccountManager(DatabaseConfig databaseConfig,
|
AndroidAccountManager(DatabaseConfig databaseConfig,
|
||||||
CryptoComponent crypto, IdentityManager identityManager,
|
CryptoComponent crypto, IdentityManager identityManager,
|
||||||
SharedPreferences prefs, Application app) {
|
SharedPreferences prefs, PersistentLogManager logManager,
|
||||||
|
Application app) {
|
||||||
super(databaseConfig, crypto, identityManager);
|
super(databaseConfig, crypto, identityManager);
|
||||||
this.prefs = prefs;
|
this.prefs = prefs;
|
||||||
|
this.logManager = logManager;
|
||||||
appContext = app.getApplicationContext();
|
appContext = app.getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +83,7 @@ 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());
|
||||||
}
|
}
|
||||||
|
replacePersistentLogger();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,4 +144,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 = appContext.getDir("log", MODE_PRIVATE);
|
||||||
|
try {
|
||||||
|
logManager.addLogHandler(logDir, getLogger(""));
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.content.pm.ApplicationInfo;
|
|||||||
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 +16,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 +30,8 @@ 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 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 +45,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 +67,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, app) {
|
||||||
@Override
|
@Override
|
||||||
SharedPreferences getDefaultSharedPreferences() {
|
SharedPreferences getDefaultSharedPreferences() {
|
||||||
return defaultPrefs;
|
return defaultPrefs;
|
||||||
@@ -109,10 +115,15 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(cacheDir));
|
will(returnValue(cacheDir));
|
||||||
oneOf(app).getExternalCacheDir();
|
oneOf(app).getExternalCacheDir();
|
||||||
will(returnValue(externalCacheDir));
|
will(returnValue(externalCacheDir));
|
||||||
|
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 +141,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());
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,4 +19,10 @@ public interface StreamDecrypterFactory {
|
|||||||
*/
|
*/
|
||||||
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
|
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
|
||||||
SecretKey headerKey);
|
SecretKey headerKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link StreamDecrypter} for decrypting a log stream.
|
||||||
|
*/
|
||||||
|
StreamDecrypter createLogStreamDecrypter(InputStream in,
|
||||||
|
SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ public interface StreamEncrypterFactory {
|
|||||||
* Creates a {@link StreamEncrypter} for encrypting a contact exchange
|
* Creates a {@link StreamEncrypter} for encrypting a contact exchange
|
||||||
* stream.
|
* stream.
|
||||||
*/
|
*/
|
||||||
StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out,
|
StreamEncrypter createContactExchangeStreamEncrypter(OutputStream out,
|
||||||
|
SecretKey headerKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link StreamEncrypter} for encrypting a log stream.
|
||||||
|
*/
|
||||||
|
StreamEncrypter createLogStreamEncrypter(OutputStream out,
|
||||||
SecretKey headerKey);
|
SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 getPersistedLog(File dir, boolean old) throws IOException;
|
||||||
|
}
|
||||||
@@ -20,4 +20,9 @@ public interface StreamReaderFactory {
|
|||||||
*/
|
*/
|
||||||
InputStream createContactExchangeStreamReader(InputStream in,
|
InputStream createContactExchangeStreamReader(InputStream in,
|
||||||
SecretKey headerKey);
|
SecretKey headerKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an {@link InputStream} for reading from a log stream.
|
||||||
|
*/
|
||||||
|
InputStream createLogStreamReader(InputStream in, SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,18 @@ import java.io.OutputStream;
|
|||||||
public interface StreamWriterFactory {
|
public interface StreamWriterFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link OutputStream OutputStream} for writing to a
|
* Creates a {@link StreamWriter} for writing to a transport stream.
|
||||||
* transport stream
|
|
||||||
*/
|
*/
|
||||||
StreamWriter createStreamWriter(OutputStream out, StreamContext ctx);
|
StreamWriter createStreamWriter(OutputStream out, StreamContext ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link OutputStream OutputStream} for writing to a contact
|
* Creates a {@link StreamWriter} for writing to a contact exchange stream.
|
||||||
* exchange stream.
|
|
||||||
*/
|
*/
|
||||||
StreamWriter createContactExchangeStreamWriter(OutputStream out,
|
StreamWriter createContactExchangeStreamWriter(OutputStream out,
|
||||||
SecretKey headerKey);
|
SecretKey headerKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link StreamWriter} for writing to a log stream.
|
||||||
|
*/
|
||||||
|
StreamWriter createLogStreamWriter(OutputStream out, SecretKey headerKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,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.plugin.PluginModule;
|
import org.briarproject.bramble.plugin.PluginModule;
|
||||||
import org.briarproject.bramble.properties.PropertiesModule;
|
import org.briarproject.bramble.properties.PropertiesModule;
|
||||||
import org.briarproject.bramble.record.RecordModule;
|
import org.briarproject.bramble.record.RecordModule;
|
||||||
@@ -41,6 +42,7 @@ import dagger.Module;
|
|||||||
IoModule.class,
|
IoModule.class,
|
||||||
KeyAgreementModule.class,
|
KeyAgreementModule.class,
|
||||||
LifecycleModule.class,
|
LifecycleModule.class,
|
||||||
|
LoggingModule.class,
|
||||||
PluginModule.class,
|
PluginModule.class,
|
||||||
PropertiesModule.class,
|
PropertiesModule.class,
|
||||||
RecordModule.class,
|
RecordModule.class,
|
||||||
|
|||||||
@@ -36,4 +36,10 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
|
|||||||
SecretKey headerKey) {
|
SecretKey headerKey) {
|
||||||
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
|
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamDecrypter createLogStreamDecrypter(InputStream in,
|
||||||
|
SecretKey headerKey) {
|
||||||
|
return createContactExchangeStreamDecrypter(in, headerKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StreamEncrypter createContactExchangeStreamDecrypter(
|
public StreamEncrypter createContactExchangeStreamEncrypter(
|
||||||
OutputStream out, SecretKey headerKey) {
|
OutputStream out, SecretKey headerKey) {
|
||||||
AuthenticatedCipher cipher = cipherProvider.get();
|
AuthenticatedCipher cipher = cipherProvider.get();
|
||||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||||
@@ -60,4 +60,10 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
|||||||
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
|
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
|
||||||
headerKey, frameKey);
|
headerKey, frameKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamEncrypter createLogStreamEncrypter(OutputStream out,
|
||||||
|
SecretKey headerKey) {
|
||||||
|
return createContactExchangeStreamEncrypter(out, headerKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
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;
|
||||||
|
|
||||||
@@ -47,6 +47,7 @@ public class BriefLogFormatter extends Formatter {
|
|||||||
sb.append('\n');
|
sb.append('\n');
|
||||||
appendThrowable(sb, t);
|
appendThrowable(sb, t);
|
||||||
}
|
}
|
||||||
|
sb.append('\n');
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.briarproject.bramble.logging;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
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 ScheduledExecutorService scheduler;
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final AtomicBoolean flushScheduled = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
FlushingStreamHandler(ScheduledExecutorService 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,
|
||||||
|
FLUSH_DELAY_MS, MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduledFlush() {
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
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,177 @@
|
|||||||
|
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.Scheduler;
|
||||||
|
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.ScheduledExecutorService;
|
||||||
|
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 ScheduledExecutorService 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(
|
||||||
|
@Scheduler ScheduledExecutorService 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 getPersistedLog(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 getPersistedLog(new File(dir, OLD_LOG_FILE), oldLogKey);
|
||||||
|
} else {
|
||||||
|
return getPersistedLog(new File(dir, LOG_FILE), logKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Scanner getPersistedLog(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,15 +24,21 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream createStreamReader(InputStream in, StreamContext ctx) {
|
public InputStream createStreamReader(InputStream in, StreamContext ctx) {
|
||||||
return new StreamReaderImpl(
|
return new StreamReaderImpl(streamDecrypterFactory
|
||||||
streamDecrypterFactory.createStreamDecrypter(in, ctx));
|
.createStreamDecrypter(in, ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream createContactExchangeStreamReader(InputStream in,
|
public InputStream createContactExchangeStreamReader(InputStream in,
|
||||||
SecretKey headerKey) {
|
SecretKey headerKey) {
|
||||||
return new StreamReaderImpl(
|
return new StreamReaderImpl(streamDecrypterFactory
|
||||||
streamDecrypterFactory.createContactExchangeStreamDecrypter(in,
|
.createContactExchangeStreamDecrypter(in, headerKey));
|
||||||
headerKey));
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream createLogStreamReader(InputStream in,
|
||||||
|
SecretKey headerKey) {
|
||||||
|
return new StreamReaderImpl(streamDecrypterFactory
|
||||||
|
.createLogStreamDecrypter(in, headerKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,15 +26,21 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
|
|||||||
@Override
|
@Override
|
||||||
public StreamWriter createStreamWriter(OutputStream out,
|
public StreamWriter createStreamWriter(OutputStream out,
|
||||||
StreamContext ctx) {
|
StreamContext ctx) {
|
||||||
return new StreamWriterImpl(
|
return new StreamWriterImpl(streamEncrypterFactory
|
||||||
streamEncrypterFactory.createStreamEncrypter(out, ctx));
|
.createStreamEncrypter(out, ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StreamWriter createContactExchangeStreamWriter(OutputStream out,
|
public StreamWriter createContactExchangeStreamWriter(OutputStream out,
|
||||||
SecretKey headerKey) {
|
SecretKey headerKey) {
|
||||||
return new StreamWriterImpl(
|
return new StreamWriterImpl(streamEncrypterFactory
|
||||||
streamEncrypterFactory.createContactExchangeStreamDecrypter(out,
|
.createContactExchangeStreamEncrypter(out, headerKey));
|
||||||
headerKey));
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamWriter createLogStreamWriter(OutputStream out,
|
||||||
|
SecretKey headerKey) {
|
||||||
|
return new StreamWriterImpl(streamEncrypterFactory
|
||||||
|
.createLogStreamEncrypter(out, headerKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import android.content.SharedPreferences;
|
|||||||
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;
|
||||||
@@ -17,8 +18,8 @@ class BriarAccountManager extends AndroidAccountManager {
|
|||||||
@Inject
|
@Inject
|
||||||
BriarAccountManager(DatabaseConfig databaseConfig, CryptoComponent crypto,
|
BriarAccountManager(DatabaseConfig databaseConfig, CryptoComponent crypto,
|
||||||
IdentityManager identityManager, SharedPreferences prefs,
|
IdentityManager identityManager, SharedPreferences prefs,
|
||||||
Application app) {
|
PersistentLogManager logManager, Application app) {
|
||||||
super(databaseConfig, crypto, identityManager, prefs, app);
|
super(databaseConfig, crypto, identityManager, prefs, logManager, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -20,6 +20,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;
|
||||||
@@ -56,6 +57,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;
|
||||||
|
|
||||||
@@ -165,6 +167,10 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
FeatureFlags featureFlags();
|
FeatureFlags featureFlags();
|
||||||
|
|
||||||
|
PersistentLogManager persistentLogManager();
|
||||||
|
|
||||||
|
Formatter formatter();
|
||||||
|
|
||||||
void inject(SignInReminderReceiver briarService);
|
void inject(SignInReminderReceiver briarService);
|
||||||
|
|
||||||
void inject(BriarService briarService);
|
void inject(BriarService briarService);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import org.acra.ReportingInteractionMode;
|
|||||||
import org.acra.annotation.ReportsCrashes;
|
import org.acra.annotation.ReportsCrashes;
|
||||||
import org.briarproject.bramble.BrambleAndroidEagerSingletons;
|
import org.briarproject.bramble.BrambleAndroidEagerSingletons;
|
||||||
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.BuildConfig;
|
import org.briarproject.briar.BuildConfig;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
@@ -28,6 +29,8 @@ import org.briarproject.briar.android.reporting.BriarReportSenderFactory;
|
|||||||
import org.briarproject.briar.android.reporting.DevReportActivity;
|
import org.briarproject.briar.android.reporting.DevReportActivity;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.logging.Handler;
|
import java.util.logging.Handler;
|
||||||
import java.util.logging.LogRecord;
|
import java.util.logging.LogRecord;
|
||||||
@@ -36,6 +39,7 @@ import java.util.logging.Logger;
|
|||||||
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.acra.ReportField.ANDROID_VERSION;
|
import static org.acra.ReportField.ANDROID_VERSION;
|
||||||
import static org.acra.ReportField.APP_VERSION_CODE;
|
import static org.acra.ReportField.APP_VERSION_CODE;
|
||||||
@@ -54,6 +58,7 @@ import static org.acra.ReportField.REPORT_ID;
|
|||||||
import static org.acra.ReportField.STACK_TRACE;
|
import static org.acra.ReportField.STACK_TRACE;
|
||||||
import static org.acra.ReportField.USER_APP_START_DATE;
|
import static org.acra.ReportField.USER_APP_START_DATE;
|
||||||
import static org.acra.ReportField.USER_CRASH_DATE;
|
import static org.acra.ReportField.USER_CRASH_DATE;
|
||||||
|
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;
|
||||||
|
|
||||||
@ReportsCrashes(
|
@ReportsCrashes(
|
||||||
@@ -120,9 +125,19 @@ 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);
|
||||||
|
|
||||||
|
applicationComponent = createApplicationComponent();
|
||||||
|
|
||||||
|
PersistentLogManager logManager =
|
||||||
|
applicationComponent.persistentLogManager();
|
||||||
|
File logDir = getDir("log", MODE_PRIVATE);
|
||||||
|
try {
|
||||||
|
rootLogger.addHandler(logManager.createLogHandler(logDir));
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
|
||||||
LOG.info("Created");
|
LOG.info("Created");
|
||||||
|
|
||||||
applicationComponent = createApplicationComponent();
|
|
||||||
EmojiManager.install(new GoogleEmojiProvider());
|
EmojiManager.install(new GoogleEmojiProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,5 +16,7 @@ public interface RequestCodes {
|
|||||||
int REQUEST_KEYGUARD_UNLOCK = 12;
|
int REQUEST_KEYGUARD_UNLOCK = 12;
|
||||||
int REQUEST_ATTACH_IMAGE = 13;
|
int REQUEST_ATTACH_IMAGE = 13;
|
||||||
int REQUEST_SAVE_ATTACHMENT = 14;
|
int REQUEST_SAVE_ATTACHMENT = 14;
|
||||||
|
int REQUEST_EXPORT_LOG = 15;
|
||||||
|
int REQUEST_EXPORT_OLD_LOG = 16;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,17 +14,21 @@ import android.os.Looper;
|
|||||||
import org.acra.builder.ReportBuilder;
|
import org.acra.builder.ReportBuilder;
|
||||||
import org.acra.builder.ReportPrimer;
|
import org.acra.builder.ReportPrimer;
|
||||||
import org.briarproject.bramble.api.Pair;
|
import org.briarproject.bramble.api.Pair;
|
||||||
|
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||||
import org.briarproject.briar.BuildConfig;
|
import org.briarproject.briar.BuildConfig;
|
||||||
|
import org.briarproject.briar.android.AndroidComponent;
|
||||||
import org.briarproject.briar.android.BriarApplication;
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
import org.briarproject.briar.android.logging.BriefLogFormatter;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Scanner;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.FutureTask;
|
import java.util.concurrent.FutureTask;
|
||||||
@@ -37,6 +41,7 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
|||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||||
import static android.content.Context.ACTIVITY_SERVICE;
|
import static android.content.Context.ACTIVITY_SERVICE;
|
||||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||||
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
import static android.content.Context.WIFI_P2P_SERVICE;
|
import static android.content.Context.WIFI_P2P_SERVICE;
|
||||||
import static android.content.Context.WIFI_SERVICE;
|
import static android.content.Context.WIFI_SERVICE;
|
||||||
import static android.net.ConnectivityManager.TYPE_MOBILE;
|
import static android.net.ConnectivityManager.TYPE_MOBILE;
|
||||||
@@ -51,6 +56,8 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
|||||||
|
|
||||||
public class BriarReportPrimer implements ReportPrimer {
|
public class BriarReportPrimer implements ReportPrimer {
|
||||||
|
|
||||||
|
private static final int MAX_PERSISTED_LOG_LINES = 1_000;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void primeReport(@NonNull Context ctx,
|
public void primeReport(@NonNull Context ctx,
|
||||||
@NonNull ReportBuilder builder) {
|
@NonNull ReportBuilder builder) {
|
||||||
@@ -81,13 +88,23 @@ public class BriarReportPrimer implements ReportPrimer {
|
|||||||
// Log
|
// Log
|
||||||
BriarApplication app =
|
BriarApplication app =
|
||||||
(BriarApplication) ctx.getApplicationContext();
|
(BriarApplication) ctx.getApplicationContext();
|
||||||
|
AndroidComponent appComponent = app.getApplicationComponent();
|
||||||
|
PersistentLogManager logManager =
|
||||||
|
appComponent.persistentLogManager();
|
||||||
|
Formatter formatter = appComponent.formatter();
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
Formatter formatter = new BriefLogFormatter();
|
|
||||||
for (LogRecord record : app.getRecentLogRecords()) {
|
for (LogRecord record : app.getRecentLogRecords()) {
|
||||||
sb.append(formatter.format(record)).append('\n');
|
sb.append(formatter.format(record));
|
||||||
}
|
}
|
||||||
customData.put("Log", sb.toString());
|
customData.put("Log", sb.toString());
|
||||||
|
|
||||||
|
customData.put("Persisted log",
|
||||||
|
getPersistedLog(ctx, logManager, false));
|
||||||
|
|
||||||
|
customData.put("Previous persisted log",
|
||||||
|
getPersistedLog(ctx, logManager, true));
|
||||||
|
|
||||||
// System memory
|
// System memory
|
||||||
Object o = ctx.getSystemService(ACTIVITY_SERVICE);
|
Object o = ctx.getSystemService(ACTIVITY_SERVICE);
|
||||||
ActivityManager am = (ActivityManager) o;
|
ActivityManager am = (ActivityManager) o;
|
||||||
@@ -252,6 +269,27 @@ public class BriarReportPrimer implements ReportPrimer {
|
|||||||
|
|
||||||
return unmodifiableMap(customData);
|
return unmodifiableMap(customData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getPersistedLog(Context ctx,
|
||||||
|
PersistentLogManager logManager, boolean old) {
|
||||||
|
File logDir = ctx.getDir("log", MODE_PRIVATE);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
try {
|
||||||
|
Scanner scanner = logManager.getPersistedLog(logDir, old);
|
||||||
|
LinkedList<String> lines = new LinkedList<>();
|
||||||
|
int numLines = 0;
|
||||||
|
while (scanner.hasNextLine()) {
|
||||||
|
lines.add(scanner.nextLine());
|
||||||
|
if (numLines == MAX_PERSISTED_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 persisted log: ").append(e);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class SingleShotAndroidExecutor extends Thread {
|
private static class SingleShotAndroidExecutor extends Thread {
|
||||||
|
|||||||
@@ -1,18 +1,65 @@
|
|||||||
package org.briarproject.briar.android.settings;
|
package org.briarproject.briar.android.settings;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.logging.PersistentLogManager;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
|
||||||
|
import static android.content.Intent.ACTION_CREATE_DOCUMENT;
|
||||||
|
import static android.content.Intent.CATEGORY_OPENABLE;
|
||||||
|
import static android.content.Intent.EXTRA_TITLE;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.os.Environment.DIRECTORY_DOWNLOADS;
|
||||||
|
import static android.os.Environment.getExternalStoragePublicDirectory;
|
||||||
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_EXPORT_LOG;
|
||||||
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_EXPORT_OLD_LOG;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
public class SettingsActivity extends BriarActivity {
|
public class SettingsActivity extends BriarActivity {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(SettingsActivity.class.getName());
|
||||||
|
|
||||||
|
private static final String LOG_EXPORT_FILENAME = "briar-log.txt";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@IoExecutor
|
||||||
|
Executor ioExecutor;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
PersistentLogManager logManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(@Nullable Bundle bundle) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
@@ -37,4 +84,96 @@ public class SettingsActivity extends BriarActivity {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int request, int result,
|
||||||
|
@Nullable Intent data) {
|
||||||
|
super.onActivityResult(request, result, data);
|
||||||
|
if (request == REQUEST_EXPORT_LOG && result == RESULT_OK &&
|
||||||
|
data != null && data.getData() != null) {
|
||||||
|
exportLog(false, data.getData());
|
||||||
|
} else if (request == REQUEST_EXPORT_OLD_LOG && result == RESULT_OK &&
|
||||||
|
data != null && data.getData() != null) {
|
||||||
|
exportLog(true, data.getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportLog(boolean old, Uri uri) {
|
||||||
|
copyLog(old, () -> getOutputStream(uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyLog(boolean old, OutputStreamProvider osp) {
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
File logDir = getApplication().getDir("log", MODE_PRIVATE);
|
||||||
|
Scanner scanner = logManager.getPersistedLog(logDir, old);
|
||||||
|
if (!scanner.hasNextLine()) {
|
||||||
|
scanner.close();
|
||||||
|
runOnUiThreadUnlessDestroyed(() ->
|
||||||
|
Toast.makeText(getApplication(), "Log is empty",
|
||||||
|
LENGTH_LONG).show());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PrintWriter w = new PrintWriter(osp.getOutputStream());
|
||||||
|
while (scanner.hasNextLine()) w.println(scanner.nextLine());
|
||||||
|
w.flush();
|
||||||
|
w.close();
|
||||||
|
scanner.close();
|
||||||
|
runOnUiThreadUnlessDestroyed(() ->
|
||||||
|
Toast.makeText(getApplication(), "Log exported",
|
||||||
|
LENGTH_LONG).show());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
runOnUiThreadUnlessDestroyed(() ->
|
||||||
|
Toast.makeText(getApplication(), "Failed to export log",
|
||||||
|
LENGTH_LONG).show());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void onExportLogClick(boolean old) {
|
||||||
|
if (SDK_INT >= 19) {
|
||||||
|
Intent intent = getExportLogIntent();
|
||||||
|
int request = old ? REQUEST_EXPORT_OLD_LOG : REQUEST_EXPORT_LOG;
|
||||||
|
startActivityForResult(intent, request);
|
||||||
|
} else {
|
||||||
|
exportLog(old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = 19)
|
||||||
|
private Intent getExportLogIntent() {
|
||||||
|
Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
|
||||||
|
intent.addCategory(CATEGORY_OPENABLE);
|
||||||
|
intent.setType("text/plain");
|
||||||
|
intent.putExtra(EXTRA_TITLE, LOG_EXPORT_FILENAME);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportLog(boolean old) {
|
||||||
|
File file = getLogOutputFile();
|
||||||
|
copyLog(old, () -> getOutputStream(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getLogOutputFile() {
|
||||||
|
File path = getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS);
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
path.mkdirs();
|
||||||
|
return new File(path, LOG_EXPORT_FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputStream getOutputStream(File file) throws IOException {
|
||||||
|
return new FileOutputStream(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputStream getOutputStream(Uri uri) throws IOException {
|
||||||
|
OutputStream os =
|
||||||
|
getApplication().getContentResolver().openOutputStream(uri);
|
||||||
|
if (os == null) throw new IOException();
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface OutputStreamProvider {
|
||||||
|
OutputStream getOutputStream() throws IOException;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.briar.android.settings;
|
package org.briarproject.briar.android.settings;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -74,6 +75,7 @@ import static android.widget.Toast.LENGTH_SHORT;
|
|||||||
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR;
|
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR;
|
||||||
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.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
|
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_MOBILE;
|
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_MOBILE;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_NETWORK;
|
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_NETWORK;
|
||||||
@@ -133,7 +135,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
"pref_key_tor_only_when_charging";
|
"pref_key_tor_only_when_charging";
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(SettingsFragment.class.getName());
|
getLogger(SettingsFragment.class.getName());
|
||||||
|
|
||||||
private SettingsActivity listener;
|
private SettingsActivity listener;
|
||||||
private ListPreference language;
|
private ListPreference language;
|
||||||
@@ -251,8 +253,25 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
throw new RuntimeException("Boom!");
|
throw new RuntimeException("Boom!");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
findPreference("pref_key_export_log").setOnPreferenceClickListener(
|
||||||
|
preference -> {
|
||||||
|
((SettingsActivity) requireActivity())
|
||||||
|
.onExportLogClick(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
findPreference("pref_key_export_old_log")
|
||||||
|
.setOnPreferenceClickListener(
|
||||||
|
preference -> {
|
||||||
|
((SettingsActivity) requireActivity())
|
||||||
|
.onExportLogClick(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
findPreference("pref_key_explode").setVisible(false);
|
findPreference("pref_key_explode").setVisible(false);
|
||||||
|
findPreference("pref_key_export_log").setVisible(false);
|
||||||
|
findPreference("pref_key_export_old_log").setVisible(false);
|
||||||
findPreference("pref_key_test_data").setVisible(false);
|
findPreference("pref_key_test_data").setVisible(false);
|
||||||
PreferenceGroup testing =
|
PreferenceGroup testing =
|
||||||
findPreference("pref_key_explode").getParent();
|
findPreference("pref_key_explode").getParent();
|
||||||
@@ -331,6 +350,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
return direction == LAYOUT_DIRECTION_LTR;
|
return direction == LAYOUT_DIRECTION_LTR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("StringFormatInvalid")
|
||||||
private void setTorNetworkSummary(int torNetworkSetting) {
|
private void setTorNetworkSummary(int torNetworkSetting) {
|
||||||
if (torNetworkSetting != PREF_TOR_NETWORK_AUTOMATIC) {
|
if (torNetworkSetting != PREF_TOR_NETWORK_AUTOMATIC) {
|
||||||
torNetwork.setSummary("%s"); // use setting value
|
torNetwork.setSummary("%s"); // use setting value
|
||||||
|
|||||||
@@ -227,6 +227,16 @@
|
|||||||
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"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="pref_key_export_old_log"
|
||||||
|
android:title="Export Previous Log to SD Card"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="pref_key_explode"
|
android:key="pref_key_explode"
|
||||||
android:title="Crash"
|
android:title="Crash"
|
||||||
|
|||||||
Reference in New Issue
Block a user