diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/reporting/DevReporter.java b/bramble-api/src/main/java/org/briarproject/bramble/api/reporting/DevReporter.java
index 9ef25b5f5..392886a2f 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/reporting/DevReporter.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/reporting/DevReporter.java
@@ -23,6 +23,8 @@ public interface DevReporter {
/**
* Sends any reports previously stored on disk.
+ *
+ * @return The number of reports that were sent.
*/
- void sendReports();
+ int sendReports();
}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/reporting/DevReporterImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/reporting/DevReporterImpl.java
index 228569fa2..e32f4757c 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/reporting/DevReporterImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/reporting/DevReporterImpl.java
@@ -29,6 +29,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.net.SocketFactory;
+import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -100,11 +101,12 @@ class DevReporterImpl implements DevReporter, EventListener {
}
@Override
- public void sendReports() {
+ public int sendReports() {
File reportDir = devConfig.getReportDir();
File[] reports = reportDir.listFiles();
+ int reportsSent = 0;
if (reports == null || reports.length == 0)
- return; // No reports to send
+ return reportsSent; // No reports to send
LOG.info("Sending reports to developers");
for (File f : reports) {
@@ -116,13 +118,15 @@ class DevReporterImpl implements DevReporter, EventListener {
in = new FileInputStream(f);
IoUtils.copyAndClose(in, out);
f.delete();
+ reportsSent++;
} catch (IOException e) {
LOG.log(WARNING, "Failed to send reports", e);
tryToClose(out, LOG, WARNING);
tryToClose(in, LOG, WARNING);
- return;
+ return reportsSent;
}
}
- LOG.info("Reports sent");
+ if (LOG.isLoggable(INFO)) LOG.info(reportsSent + " report(s) sent");
+ return reportsSent;
}
}
diff --git a/briar-android/build.gradle b/briar-android/build.gradle
index 489c5a248..6d6a46b88 100644
--- a/briar-android/build.gradle
+++ b/briar-android/build.gradle
@@ -100,7 +100,6 @@ dependencies {
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0-rc03'
- implementation 'ch.acra:acra:4.11'
implementation 'info.guardianproject.panic:panic:1.0'
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
implementation 'de.hdodenhof:circleimageview:3.0.1'
diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml
index a54a99b34..8ea033382 100644
--- a/briar-android/src/main/AndroidManifest.xml
+++ b/briar-android/src/main/AndroidManifest.xml
@@ -68,14 +68,21 @@
android:exported="false">
+ android:windowSoftInputMode="adjustResize|stateHidden" />
+
-1) {
+ timeInfo.add("AppStartTime", formatTime(startTime));
+ }
+ return new ReportItem("TimeInfo", R.string.dev_report_time_info,
+ timeInfo);
+ }
+
+ private String formatTime(long time) {
+ SimpleDateFormat format =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", US);
+ format.setTimeZone(getTimeZone("UTC"));
+ return format.format(new Date(time));
+ }
+
+ private ReportItem getMemory() {
+ MultiReportInfo memInfo = new MultiReportInfo();
+
+ // System memory
+ ActivityManager am = getSystemService(ctx, ActivityManager.class);
+ ActivityManager.MemoryInfo mem = new ActivityManager.MemoryInfo();
+ requireNonNull(am).getMemoryInfo(mem);
+ memInfo.add("SystemMemoryTotal", mem.totalMem);
+ memInfo.add("SystemMemoryFree", mem.availMem);
+ memInfo.add("SystemMemoryThreshold", mem.threshold);
+
+ // Virtual machine memory
+ Runtime runtime = Runtime.getRuntime();
+ memInfo.add("VirtualMachineMemoryAllocated", runtime.totalMemory());
+ memInfo.add("VirtualMachineMemoryFree", runtime.freeMemory());
+ memInfo.add("VirtualMachineMemoryMaximum", runtime.maxMemory());
+
+ return new ReportItem("Memory", R.string.dev_report_memory, memInfo);
+ }
+
+ private ReportItem getStorage() {
+ MultiReportInfo storageInfo = new MultiReportInfo();
+
+ // Internal storage
+ File root = Environment.getRootDirectory();
+ storageInfo.add("InternalStorageTotal", root.getTotalSpace());
+ storageInfo.add("InternalStorageFree", root.getFreeSpace());
+
+ // External storage (SD card)
+ File sd = Environment.getExternalStorageDirectory();
+ storageInfo.add("ExternalStorageTotal", sd.getTotalSpace());
+ storageInfo.add("ExternalStorageFree", sd.getFreeSpace());
+
+ return new ReportItem("Storage", R.string.dev_report_storage,
+ storageInfo);
+ }
+
+ private ReportItem getConnectivity() {
+ MultiReportInfo connectivityInfo = new MultiReportInfo();
+
+ // Is mobile data available?
+ ConnectivityManager cm = requireNonNull(
+ getSystemService(ctx, ConnectivityManager.class));
+ NetworkInfo mobile = cm.getNetworkInfo(TYPE_MOBILE);
+ boolean mobileAvailable = mobile != null && mobile.isAvailable();
+ connectivityInfo.add("MobileDataAvailable", mobileAvailable);
+
+ // Is mobile data enabled?
+ boolean mobileEnabled = false;
+ try {
+ Class> clazz = Class.forName(cm.getClass().getName());
+ Method method = clazz.getDeclaredMethod("getMobileDataEnabled");
+ method.setAccessible(true);
+ mobileEnabled = (Boolean) requireNonNull(method.invoke(cm));
+ } catch (ClassNotFoundException
+ | NoSuchMethodException
+ | IllegalArgumentException
+ | InvocationTargetException
+ | IllegalAccessException e) {
+ connectivityInfo
+ .add("MobileDataReflectionException", e.toString());
+ }
+ connectivityInfo.add("MobileDataEnabled", mobileEnabled);
+
+ // Is mobile data connected ?
+ boolean mobileConnected = mobile != null && mobile.isConnected();
+ connectivityInfo.add("MobileDataConnected", mobileConnected);
+
+ // Is wifi available?
+ NetworkInfo wifi = cm.getNetworkInfo(TYPE_WIFI);
+ boolean wifiAvailable = wifi != null && wifi.isAvailable();
+ connectivityInfo.add("WifiAvailable", wifiAvailable);
+
+ // Is wifi enabled?
+ WifiManager wm = getSystemService(ctx, WifiManager.class);
+ boolean wifiEnabled = wm != null &&
+ wm.getWifiState() == WIFI_STATE_ENABLED;
+ connectivityInfo.add("WifiEnabled", wifiEnabled);
+
+ // Is wifi connected?
+ boolean wifiConnected = wifi != null && wifi.isConnected();
+ connectivityInfo.add("WifiConnected", wifiConnected);
+
+ // Is wifi direct supported?
+ boolean wifiDirect = ctx.getSystemService(WIFI_P2P_SERVICE) != null;
+ connectivityInfo.add("WiFiDirectSupported", wifiDirect);
+
+ if (wm != null) {
+ WifiInfo wifiInfo = wm.getConnectionInfo();
+ if (wifiInfo != null) {
+ int ip = wifiInfo.getIpAddress(); // Nice API, Google
+ byte[] ipBytes = new byte[4];
+ ipBytes[0] = (byte) (ip & 0xFF);
+ ipBytes[1] = (byte) ((ip >> 8) & 0xFF);
+ ipBytes[2] = (byte) ((ip >> 16) & 0xFF);
+ ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
+ try {
+ InetAddress address = InetAddress.getByAddress(ipBytes);
+ connectivityInfo.add("WiFiAddress",
+ scrubInetAddress(address));
+ } catch (UnknownHostException ignored) {
+ // Should only be thrown if address has illegal length
+ }
+ }
+ }
+
+ // Is Bluetooth available?
+ BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
+ if (bt == null) {
+ connectivityInfo.add("BluetoothAvailable", false);
+ } else {
+ connectivityInfo.add("BluetoothAvailable", true);
+
+ // Is Bluetooth enabled?
+ @SuppressLint("HardwareIds")
+ boolean btEnabled = bt.isEnabled()
+ && !isNullOrEmpty(bt.getAddress());
+ connectivityInfo.add("BluetoothEnabled", btEnabled);
+
+ // Is Bluetooth connectable?
+ int scanMode = bt.getScanMode();
+ boolean btConnectable = scanMode == SCAN_MODE_CONNECTABLE ||
+ scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
+ connectivityInfo.add("BluetoothConnectable", btConnectable);
+
+ // Is Bluetooth discoverable?
+ boolean btDiscoverable =
+ scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
+ connectivityInfo.add("BluetoothDiscoverable", btDiscoverable);
+
+ if (SDK_INT >= 21) {
+ // Is Bluetooth LE scanning and advertising supported?
+ boolean btLeScan = bt.getBluetoothLeScanner() != null;
+ connectivityInfo.add("BluetoothLeScanningSupported", btLeScan);
+ boolean btLeAdvertise =
+ bt.getBluetoothLeAdvertiser() != null;
+ connectivityInfo.add("BluetoothLeAdvertisingSupported",
+ btLeAdvertise);
+ }
+
+ Pair p = getBluetoothAddressAndMethod(ctx, bt);
+ String address = p.getFirst();
+ String method = p.getSecond();
+ connectivityInfo.add("BluetoothAddress", scrubMacAddress(address));
+ connectivityInfo.add("BluetoothAddressMethod", method);
+ }
+ return new ReportItem("Connectivity", R.string.dev_report_connectivity,
+ connectivityInfo);
+ }
+
+ private ReportItem getBuildConfig() {
+ MultiReportInfo buildConfig = new MultiReportInfo()
+ .add("GitHash", BuildConfig.GitHash)
+ .add("BuildType", BuildConfig.BUILD_TYPE)
+ .add("Flavor", BuildConfig.FLAVOR)
+ .add("Debug", BuildConfig.DEBUG)
+ .add("BuildTimestamp", formatTime(BuildConfig.BuildTimestamp));
+ return new ReportItem("BuildConfig", R.string.dev_report_build_config,
+ buildConfig);
+ }
+
+ private ReportItem getLogcat() {
+ 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');
+ }
+ return new ReportItem("Logcat", R.string.dev_report_logcat,
+ sb.toString());
+ }
+
+ private ReportItem getDeviceFeatures() {
+ PackageManager pm = ctx.getPackageManager();
+ FeatureInfo[] features = pm.getSystemAvailableFeatures();
+ MultiReportInfo deviceFeatures = new MultiReportInfo();
+ for (FeatureInfo feature : features) {
+ String featureName = feature.name;
+ if (featureName != null) {
+ deviceFeatures.add(featureName, true);
+ } else {
+ deviceFeatures.add("glEsVersion", feature.getGlEsVersion());
+ }
+ }
+ return new ReportItem("DeviceFeatures",
+ R.string.dev_report_device_features, deviceFeatures);
+ }
+
+}
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
deleted file mode 100644
index 8c5e3e6e3..000000000
--- a/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarReportPrimer.java
+++ /dev/null
@@ -1,277 +0,0 @@
-package org.briarproject.briar.android.reporting;
-
-import android.app.ActivityManager;
-import android.bluetooth.BluetoothAdapter;
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Looper;
-
-import org.acra.builder.ReportBuilder;
-import org.acra.builder.ReportPrimer;
-import org.briarproject.bramble.api.Pair;
-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;
-import java.lang.reflect.Method;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.LinkedHashMap;
-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 androidx.annotation.NonNull;
-
-import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
-import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
-import static android.content.Context.ACTIVITY_SERVICE;
-import static android.content.Context.CONNECTIVITY_SERVICE;
-import static android.content.Context.WIFI_P2P_SERVICE;
-import static android.content.Context.WIFI_SERVICE;
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
-import static android.os.Build.VERSION.SDK_INT;
-import static java.util.Collections.unmodifiableMap;
-import static org.briarproject.bramble.util.AndroidUtils.getBluetoothAddressAndMethod;
-import static org.briarproject.bramble.util.PrivacyUtils.scrubInetAddress;
-import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
-import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
-
-public class BriarReportPrimer implements ReportPrimer {
-
- @Override
- public void primeReport(@NonNull Context ctx,
- @NonNull ReportBuilder builder) {
- CustomDataTask task = new CustomDataTask(ctx);
- FutureTask