From 17a71441943c6e53ba29bfac23b023d8477d615e Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Tue, 15 May 2018 18:04:34 +0100
Subject: [PATCH] Add internal logger.

---
 .../briar/android/BriarApplication.java       |  5 ++
 .../briar/android/BriarApplicationImpl.java   | 26 +++++++++-
 .../briar/android/TestingConstants.java       | 11 -----
 .../android/logging/BriefLogFormatter.java    | 47 ++++++++++++++++++
 .../android/logging/CachingLogHandler.java    | 48 +++++++++++++++++++
 .../android/reporting/BriarReportPrimer.java  | 17 ++++++-
 .../briar/android/TestBriarApplication.java   |  9 ++++
 7 files changed, 149 insertions(+), 14 deletions(-)
 create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/logging/BriefLogFormatter.java
 create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/logging/CachingLogHandler.java

diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java
index bf78cecfa1..e537bd86d6 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java
@@ -1,10 +1,15 @@
 package org.briarproject.briar.android;
 
+import java.util.Collection;
+import java.util.logging.LogRecord;
+
 /**
  * This exists so that the Application object will not necessarily be cast
  * directly to the Briar application object.
  */
 public interface BriarApplication {
 
+	Collection<LogRecord> getRecentLogRecords();
+
 	AndroidComponent getApplicationComponent();
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java
index 19ea60a892..d83a91bb4e 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java
@@ -12,12 +12,17 @@ import org.acra.annotation.ReportsCrashes;
 import org.briarproject.bramble.BrambleCoreModule;
 import org.briarproject.briar.BriarCoreModule;
 import org.briarproject.briar.R;
+import org.briarproject.briar.android.logging.CachingLogHandler;
 import org.briarproject.briar.android.reporting.BriarReportPrimer;
 import org.briarproject.briar.android.reporting.BriarReportSenderFactory;
 import org.briarproject.briar.android.reporting.DevReportActivity;
 
+import java.util.Collection;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
+import static java.util.logging.Level.INFO;
 import static org.acra.ReportField.ANDROID_VERSION;
 import static org.acra.ReportField.APP_VERSION_CODE;
 import static org.acra.ReportField.APP_VERSION_NAME;
@@ -36,7 +41,7 @@ import static org.acra.ReportField.REPORT_ID;
 import static org.acra.ReportField.STACK_TRACE;
 import static org.acra.ReportField.USER_APP_START_DATE;
 import static org.acra.ReportField.USER_CRASH_DATE;
-import static org.briarproject.briar.android.TestingConstants.DEFAULT_LOG_LEVEL;
+import static org.briarproject.briar.android.TestingConstants.IS_BETA_BUILD;
 import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
 
 @ReportsCrashes(
@@ -66,6 +71,8 @@ public class BriarApplicationImpl extends Application
 	private static final Logger LOG =
 			Logger.getLogger(BriarApplicationImpl.class.getName());
 
+	private final CachingLogHandler logHandler = new CachingLogHandler();
+
 	private AndroidComponent applicationComponent;
 
 	@Override
@@ -79,7 +86,17 @@ public class BriarApplicationImpl extends Application
 		super.onCreate();
 
 		if (IS_DEBUG_BUILD) enableStrictMode();
-		Logger.getLogger("").setLevel(DEFAULT_LOG_LEVEL);
+
+		Logger rootLogger = Logger.getLogger("");
+		if (!IS_DEBUG_BUILD && !IS_BETA_BUILD) {
+			// Remove default log handlers so system log is not used
+			for (Handler handler : rootLogger.getHandlers()) {
+				rootLogger.removeHandler(handler);
+			}
+		}
+		rootLogger.addHandler(logHandler);
+		rootLogger.setLevel(INFO);
+
 		LOG.info("Created");
 
 		applicationComponent = DaggerAndroidComponent.builder()
@@ -104,6 +121,11 @@ public class BriarApplicationImpl extends Application
 		StrictMode.setVmPolicy(vmPolicy.build());
 	}
 
+	@Override
+	public Collection<LogRecord> getRecentLogRecords() {
+		return logHandler.getRecentLogRecords();
+	}
+
 	@Override
 	public AndroidComponent getApplicationComponent() {
 		return applicationComponent;
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/TestingConstants.java b/briar-android/src/main/java/org/briarproject/briar/android/TestingConstants.java
index 96f1b5cf25..e3b970dc9c 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/TestingConstants.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/TestingConstants.java
@@ -2,11 +2,6 @@ package org.briarproject.briar.android;
 
 import org.briarproject.briar.BuildConfig;
 
-import java.util.logging.Level;
-
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.OFF;
-
 public interface TestingConstants {
 
 	/**
@@ -20,12 +15,6 @@ public interface TestingConstants {
 	 */
 	boolean IS_BETA_BUILD = false;
 
-	/**
-	 * Default log level. Disable logging for final release builds.
-	 */
-	@SuppressWarnings("ConstantConditions")
-	Level DEFAULT_LOG_LEVEL = IS_DEBUG_BUILD || IS_BETA_BUILD ? INFO : OFF;
-
 	/**
 	 * Whether to prevent screenshots from being taken. Setting this to true
 	 * prevents Recent Apps from storing screenshots of private information.
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/logging/BriefLogFormatter.java b/briar-android/src/main/java/org/briarproject/briar/android/logging/BriefLogFormatter.java
new file mode 100644
index 0000000000..681162aece
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/logging/BriefLogFormatter.java
@@ -0,0 +1,47 @@
+package org.briarproject.briar.android.logging;
+
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.logging.Formatter;
+import java.util.logging.LogRecord;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+import static java.util.Locale.US;
+
+@ThreadSafe
+@NotNullByDefault
+public class BriefLogFormatter extends Formatter {
+
+	private final Object lock = new Object();
+	private final DateFormat dateFormat; // Locking: lock
+	private final Date date; // Locking: lock
+
+	public BriefLogFormatter() {
+		synchronized (lock) {
+			dateFormat = new SimpleDateFormat("MM-dd HH:mm:ss.SSS ", US);
+			dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+			date = new Date();
+		}
+	}
+
+	@Override
+	public String format(LogRecord record) {
+		String dateString;
+		synchronized (lock) {
+			date.setTime(record.getMillis());
+			dateString = dateFormat.format(date);
+		}
+		StringBuilder sb = new StringBuilder(dateString);
+		sb.append(record.getLevel().getName().charAt(0)).append('/');
+		String tag = record.getLoggerName();
+		tag = tag.substring(tag.lastIndexOf('.') + 1);
+		sb.append(tag).append(": ");
+		sb.append(record.getMessage());
+		return sb.toString();
+	}
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/logging/CachingLogHandler.java b/briar-android/src/main/java/org/briarproject/briar/android/logging/CachingLogHandler.java
new file mode 100644
index 0000000000..baa51e3f6c
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/logging/CachingLogHandler.java
@@ -0,0 +1,48 @@
+package org.briarproject.briar.android.logging;
+
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+@ThreadSafe
+@NotNullByDefault
+public class CachingLogHandler extends Handler {
+
+	private static final int MAX_RECENT_RECORDS = 100;
+
+	private final Object lock = new Object();
+	// Locking: lock
+	private final Queue<LogRecord> recent = new LinkedList<>();
+
+	@Override
+	public void publish(LogRecord record) {
+		synchronized (lock) {
+			recent.add(record);
+			if (recent.size() > MAX_RECENT_RECORDS) recent.poll();
+		}
+	}
+
+	@Override
+	public void flush() {
+	}
+
+	@Override
+	public void close() {
+		synchronized (lock) {
+			recent.clear();
+		}
+	}
+
+	public Collection<LogRecord> getRecentLogRecords() {
+		synchronized (lock) {
+			return new ArrayList<>(recent);
+		}
+	}
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarReportPrimer.java b/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarReportPrimer.java
index f74647eba6..dbf6732422 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarReportPrimer.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarReportPrimer.java
@@ -18,6 +18,8 @@ import org.acra.builder.ReportBuilder;
 import org.acra.builder.ReportPrimer;
 import org.briarproject.bramble.util.StringUtils;
 import org.briarproject.briar.BuildConfig;
+import org.briarproject.briar.android.BriarApplication;
+import org.briarproject.briar.android.logging.BriefLogFormatter;
 
 import java.io.File;
 import java.lang.reflect.InvocationTargetException;
@@ -28,6 +30,8 @@ import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.FutureTask;
+import java.util.logging.Formatter;
+import java.util.logging.LogRecord;
 
 import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
 import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
@@ -69,6 +73,16 @@ public class BriarReportPrimer implements ReportPrimer {
 		public Map<String, String> call() {
 			Map<String, String> customData = new LinkedHashMap<>();
 
+			// Log
+			BriarApplication app =
+					(BriarApplication) ctx.getApplicationContext();
+			StringBuilder sb = new StringBuilder();
+			Formatter formatter = new BriefLogFormatter();
+			for (LogRecord record : app.getRecentLogRecords()) {
+				sb.append(formatter.format(record)).append('\n');
+			}
+			customData.put("Log", sb.toString());
+
 			// System memory
 			Object o = ctx.getSystemService(ACTIVITY_SERVICE);
 			ActivityManager am = (ActivityManager) o;
@@ -223,9 +237,10 @@ public class BriarReportPrimer implements ReportPrimer {
 				customData.put("Bluetooth LE status", btLeStatus);
 			}
 
-			if (bt != null)
+			if (bt != null) {
 				customData.put("Bluetooth address",
 						scrubMacAddress(bt.getAddress()));
+			}
 			String btSettingsAddr;
 			try {
 				btSettingsAddr = Settings.Secure.getString(
diff --git a/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java b/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java
index bc6016caaa..6f232bef0a 100644
--- a/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java
+++ b/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java
@@ -5,8 +5,12 @@ import android.app.Application;
 import org.briarproject.bramble.BrambleCoreModule;
 import org.briarproject.briar.BriarCoreModule;
 
+import java.util.Collection;
+import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
+import static java.util.Collections.emptyList;
+
 /**
  * This class only exists to avoid static initialisation of ACRA
  */
@@ -34,6 +38,11 @@ public class TestBriarApplication extends Application
 		AndroidEagerSingletons.initEagerSingletons(applicationComponent);
 	}
 
+	@Override
+	public Collection<LogRecord> getRecentLogRecords() {
+		return emptyList();
+	}
+
 	@Override
 	public AndroidComponent getApplicationComponent() {
 		return applicationComponent;
-- 
GitLab