mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 11:19:04 +01:00
Migrate crash reports to ACRA
This commit is contained in:
@@ -21,6 +21,7 @@ import org.briarproject.android.keyagreement.KeyAgreementActivity;
|
||||
import org.briarproject.android.keyagreement.ShowQrCodeFragment;
|
||||
import org.briarproject.android.panic.PanicPreferencesActivity;
|
||||
import org.briarproject.android.panic.PanicResponderActivity;
|
||||
import org.briarproject.android.util.BriarReportSender;
|
||||
import org.briarproject.plugins.AndroidPluginsModule;
|
||||
import org.briarproject.system.AndroidSystemModule;
|
||||
|
||||
@@ -93,4 +94,6 @@ public interface AndroidComponent extends CoreEagerSingletons {
|
||||
|
||||
// Eager singleton load
|
||||
void inject(AppModule.EagerSingletons init);
|
||||
|
||||
void inject(BriarReportSender briarReportSender);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,24 @@ package org.briarproject.android;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import org.acra.ACRA;
|
||||
import org.acra.ReportingInteractionMode;
|
||||
import org.acra.annotation.ReportsCrashes;
|
||||
import org.briarproject.CoreModule;
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.util.BriarReportPrimer;
|
||||
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@ReportsCrashes(
|
||||
reportPrimerClass = BriarReportPrimer.class,
|
||||
logcatArguments = {"-d", "-v", "time", "*:I"},
|
||||
reportSenderFactoryClasses = {
|
||||
org.briarproject.android.util.BriarReportSenderFactory.class},
|
||||
mode = ReportingInteractionMode.DIALOG,
|
||||
reportDialogClass = CrashReportActivity.class,
|
||||
resDialogOkToast = R.string.crash_report_saved
|
||||
)
|
||||
public class BriarApplication extends Application {
|
||||
|
||||
private static final Logger LOG =
|
||||
@@ -15,15 +28,18 @@ public class BriarApplication extends Application {
|
||||
|
||||
private AndroidComponent applicationComponent;
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
|
||||
// The following line triggers the initialization of ACRA
|
||||
ACRA.init(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
LOG.info("Created");
|
||||
UncaughtExceptionHandler oldHandler =
|
||||
Thread.getDefaultUncaughtExceptionHandler();
|
||||
Context ctx = getApplicationContext();
|
||||
CrashHandler newHandler = new CrashHandler(ctx, oldHandler);
|
||||
Thread.setDefaultUncaughtExceptionHandler(newHandler);
|
||||
|
||||
applicationComponent = DaggerAndroidComponent.builder()
|
||||
.appModule(new AppModule(this))
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package org.briarproject.android;
|
||||
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
class CrashHandler implements UncaughtExceptionHandler {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(CrashHandler.class.getName());
|
||||
|
||||
private final Context ctx;
|
||||
private final UncaughtExceptionHandler delegate; // May be null
|
||||
|
||||
CrashHandler(Context ctx, UncaughtExceptionHandler delegate) {
|
||||
this.ctx = ctx;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||
LOG.log(WARNING, "Uncaught exception", throwable);
|
||||
// Don't handle more than one exception
|
||||
Thread.setDefaultUncaughtExceptionHandler(delegate);
|
||||
// Get the stack trace
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
throwable.printStackTrace(pw);
|
||||
String stackTrace = sw.toString();
|
||||
// Launch the crash reporting dialog
|
||||
Intent i = new Intent();
|
||||
i.setAction("org.briarproject.REPORT_CRASH");
|
||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||
i.putExtra("briar.STACK_TRACE", stackTrace);
|
||||
i.putExtra("briar.PID", android.os.Process.myPid());
|
||||
ctx.startActivity(i);
|
||||
// Pass the exception to the default handler, if any
|
||||
if (delegate != null) delegate.uncaughtException(thread, throwable);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +1,58 @@
|
||||
package org.briarproject.android;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityManager;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.provider.Settings;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.acra.ACRA;
|
||||
import org.acra.ACRAConstants;
|
||||
import org.acra.ReportField;
|
||||
import org.acra.collector.CrashReportData;
|
||||
import org.acra.dialog.BaseCrashReportDialog;
|
||||
import org.acra.file.CrashReportPersister;
|
||||
import org.acra.prefs.SharedPreferencesFactory;
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.util.AndroidUtils;
|
||||
import org.briarproject.api.reporting.DevReporter;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Scanner;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||
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.view.View.GONE;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
public class CrashReportActivity extends AppCompatActivity
|
||||
implements OnClickListener {
|
||||
public class CrashReportActivity extends BaseCrashReportDialog
|
||||
implements DialogInterface.OnClickListener,
|
||||
DialogInterface.OnCancelListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(CrashReportActivity.class.getName());
|
||||
|
||||
private static final String STATE_REVIEWING = "reviewing";
|
||||
|
||||
private SharedPreferencesFactory sharedPreferencesFactory;
|
||||
private EditText userCommentView = null;
|
||||
private EditText userEmailView = null;
|
||||
private LinearLayout status = null;
|
||||
private View progress = null;
|
||||
|
||||
@Inject
|
||||
protected DevReporter reporter;
|
||||
|
||||
private volatile String stack = null;
|
||||
private volatile int pid = -1;
|
||||
private volatile BluetoothAdapter bt = null;
|
||||
boolean reviewing;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
@@ -77,46 +62,78 @@ public class CrashReportActivity extends AppCompatActivity
|
||||
((BriarApplication) getApplication()).getApplicationComponent()
|
||||
.inject(this);
|
||||
|
||||
sharedPreferencesFactory =
|
||||
new SharedPreferencesFactory(getApplicationContext(),
|
||||
getConfig());
|
||||
|
||||
userCommentView = (EditText) findViewById(R.id.user_comment);
|
||||
userEmailView = (EditText) findViewById(R.id.user_email);
|
||||
status = (LinearLayout) findViewById(R.id.crash_status);
|
||||
progress = findViewById(R.id.progress_wheel);
|
||||
|
||||
findViewById(R.id.share_crash_report).setOnClickListener(this);
|
||||
findViewById(R.id.share_crash_report).setOnClickListener(
|
||||
new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
processReport();
|
||||
}
|
||||
});
|
||||
|
||||
Intent i = getIntent();
|
||||
stack = i.getStringExtra("briar.STACK_TRACE");
|
||||
pid = i.getIntExtra("briar.PID", -1);
|
||||
bt = BluetoothAdapter.getDefaultAdapter();
|
||||
final SharedPreferences prefs = sharedPreferencesFactory.create();
|
||||
String userEmail = prefs.getString(ACRA.PREF_USER_EMAIL_ADDRESS, "");
|
||||
userEmailView.setText(userEmail);
|
||||
|
||||
if (state != null)
|
||||
reviewing = state.getBoolean(STATE_REVIEWING, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (!reviewing) showDialog();
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle state) {
|
||||
super.onSaveInstanceState(state);
|
||||
state.putBoolean(STATE_REVIEWING, reviewing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// show home screen, otherwise we are crashing again
|
||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||
intent.addCategory(Intent.CATEGORY_HOME);
|
||||
startActivity(intent);
|
||||
//Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||
//intent.addCategory(Intent.CATEGORY_HOME);
|
||||
//startActivity(intent);
|
||||
closeReport();
|
||||
}
|
||||
|
||||
public void onClick(View view) {
|
||||
// TODO Encapsulate the dialog in a re-usable fragment
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||
dialog.dismiss();
|
||||
} else {
|
||||
dialog.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
closeReport();
|
||||
}
|
||||
|
||||
private void showDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this,
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setTitle(R.string.dialog_title_share_crash_report);
|
||||
builder.setMessage(R.string.dialog_message_share_crash_report);
|
||||
builder.setNegativeButton(R.string.cancel_button, null);
|
||||
builder.setPositiveButton(R.string.send,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
saveCrashReport();
|
||||
}
|
||||
});
|
||||
builder.setTitle(R.string.dialog_title_share_crash_report)
|
||||
.setIcon(R.drawable.ic_warning_black_24dp)
|
||||
.setMessage(R.string.dialog_message_share_crash_report)
|
||||
.setPositiveButton(R.string.dialog_button_ok, this)
|
||||
.setNegativeButton(R.string.cancel_button, this);
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
dialog.setOnCancelListener(this);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@@ -124,21 +141,40 @@ public class CrashReportActivity extends AppCompatActivity
|
||||
status.setVisibility(INVISIBLE);
|
||||
progress.setVisibility(VISIBLE);
|
||||
status.removeAllViews();
|
||||
new AsyncTask<Void, Void, Map<String, String>>() {
|
||||
new AsyncTask<Void, Void, CrashReportData>() {
|
||||
|
||||
@Override
|
||||
protected Map<String, String> doInBackground(Void... args) {
|
||||
return getStatusMap();
|
||||
protected CrashReportData doInBackground(Void... args) {
|
||||
File reportFile = (File) getIntent().getSerializableExtra(
|
||||
ACRAConstants.EXTRA_REPORT_FILE);
|
||||
final CrashReportPersister persister =
|
||||
new CrashReportPersister();
|
||||
try {
|
||||
return persister.load(reportFile);
|
||||
} catch (IOException e) {
|
||||
LOG.log(WARNING, "Could not load report file", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Map<String, String> result) {
|
||||
for (Entry<String, String> e : result.entrySet()) {
|
||||
View v = getLayoutInflater()
|
||||
.inflate(R.layout.list_item_crash, status, false);
|
||||
((TextView) v.findViewById(R.id.title)).setText(e.getKey());
|
||||
((TextView) v.findViewById(R.id.content))
|
||||
.setText(e.getValue());
|
||||
protected void onPostExecute(CrashReportData crashData) {
|
||||
LayoutInflater inflater = getLayoutInflater();
|
||||
if (crashData != null) {
|
||||
for (Entry<ReportField, String> e : crashData.entrySet()) {
|
||||
View v = inflater.inflate(R.layout.list_item_crash,
|
||||
status, false);
|
||||
((TextView) v.findViewById(R.id.title))
|
||||
.setText(e.getKey().toString());
|
||||
((TextView) v.findViewById(R.id.content))
|
||||
.setText(e.getValue());
|
||||
status.addView(v);
|
||||
}
|
||||
} else {
|
||||
View v = inflater.inflate(
|
||||
android.R.layout.simple_list_item_1, status, false);
|
||||
((TextView) v.findViewById(android.R.id.text1))
|
||||
.setText(R.string.could_not_load_crash_data);
|
||||
status.addView(v);
|
||||
}
|
||||
status.setVisibility(VISIBLE);
|
||||
@@ -147,227 +183,28 @@ public class CrashReportActivity extends AppCompatActivity
|
||||
}.execute();
|
||||
}
|
||||
|
||||
// FIXME: Load strings from resources if we're keeping this activity
|
||||
@SuppressLint("NewApi")
|
||||
private Map<String, String> getStatusMap() {
|
||||
Map<String, String> statusMap = new LinkedHashMap<String, String>();
|
||||
private void processReport() {
|
||||
// Retrieve user comment
|
||||
final String comment = userCommentView != null ?
|
||||
userCommentView.getText().toString() : "";
|
||||
|
||||
// Device type
|
||||
String deviceType;
|
||||
String manufacturer = Build.MANUFACTURER;
|
||||
String model = Build.MODEL;
|
||||
String brand = Build.BRAND;
|
||||
if (model.startsWith(manufacturer)) deviceType = capitalize(model);
|
||||
else deviceType = capitalize(manufacturer) + " " + model;
|
||||
if (!StringUtils.isNullOrEmpty(brand))
|
||||
deviceType += " (" + capitalize(brand) + ")";
|
||||
statusMap.put("Device type:", deviceType);
|
||||
|
||||
// Android version
|
||||
String release = Build.VERSION.RELEASE;
|
||||
int sdk = Build.VERSION.SDK_INT;
|
||||
statusMap.put("Android version:", release + " (" + sdk + ")");
|
||||
|
||||
// CPU architectures
|
||||
Collection<String> abis = AndroidUtils.getSupportedArchitectures();
|
||||
String joined = StringUtils.join(abis, ", ");
|
||||
statusMap.put("Architecture:", joined);
|
||||
|
||||
// System memory
|
||||
Object o = getSystemService(ACTIVITY_SERVICE);
|
||||
ActivityManager am = (ActivityManager) o;
|
||||
ActivityManager.MemoryInfo mem = new ActivityManager.MemoryInfo();
|
||||
am.getMemoryInfo(mem);
|
||||
String systemMemory;
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
systemMemory = (mem.totalMem / 1024 / 1024) + " MiB total, "
|
||||
+ (mem.availMem / 1024 / 1204) + " MiB free, "
|
||||
+ (mem.threshold / 1024 / 1024) + " MiB threshold";
|
||||
// Store the user email
|
||||
final String userEmail;
|
||||
final SharedPreferences prefs = sharedPreferencesFactory.create();
|
||||
if (userEmailView != null) {
|
||||
userEmail = userEmailView.getText().toString();
|
||||
final SharedPreferences.Editor prefEditor = prefs.edit();
|
||||
prefEditor.putString(ACRA.PREF_USER_EMAIL_ADDRESS, userEmail);
|
||||
prefEditor.commit();
|
||||
} else {
|
||||
systemMemory = (mem.availMem / 1024 / 1204) + " MiB free, "
|
||||
+ (mem.threshold / 1024 / 1024) + " MiB threshold";
|
||||
userEmail = prefs.getString(ACRA.PREF_USER_EMAIL_ADDRESS, "");
|
||||
}
|
||||
statusMap.put("System memory:", systemMemory);
|
||||
|
||||
// Virtual machine memory
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long heap = runtime.totalMemory();
|
||||
long heapFree = runtime.freeMemory();
|
||||
long heapMax = runtime.maxMemory();
|
||||
String vmMemory = (heap / 1024 / 1024) + " MiB allocated, "
|
||||
+ (heapFree / 1024 / 1024) + " MiB free, "
|
||||
+ (heapMax / 1024 / 1024) + " MiB maximum";
|
||||
statusMap.put("Virtual machine memory:", vmMemory);
|
||||
|
||||
// Internal storage
|
||||
File root = Environment.getRootDirectory();
|
||||
long rootTotal = root.getTotalSpace();
|
||||
long rootFree = root.getFreeSpace();
|
||||
String internal = (rootTotal / 1024 / 1024) + " MiB total, "
|
||||
+ (rootFree / 1024 / 1024) + " MiB free";
|
||||
statusMap.put("Internal storage:", internal);
|
||||
|
||||
// External storage (SD card)
|
||||
File sd = Environment.getExternalStorageDirectory();
|
||||
long sdTotal = sd.getTotalSpace();
|
||||
long sdFree = sd.getFreeSpace();
|
||||
String external = (sdTotal / 1024 / 1024) + " MiB total, "
|
||||
+ (sdFree / 1024 / 1024) + " MiB free";
|
||||
statusMap.put("External storage:", external);
|
||||
|
||||
// Is mobile data available?
|
||||
o = getSystemService(CONNECTIVITY_SERVICE);
|
||||
ConnectivityManager cm = (ConnectivityManager) o;
|
||||
NetworkInfo mobile = cm.getNetworkInfo(TYPE_MOBILE);
|
||||
boolean mobileAvailable = mobile != null && mobile.isAvailable();
|
||||
// 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) method.invoke(cm);
|
||||
} catch (ClassNotFoundException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (IllegalAccessException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (InvocationTargetException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
// Is mobile data connected ?
|
||||
boolean mobileConnected = mobile != null && mobile.isConnected();
|
||||
|
||||
String mobileStatus;
|
||||
if (mobileAvailable) mobileStatus = "Available, ";
|
||||
else mobileStatus = "Not available, ";
|
||||
if (mobileEnabled) mobileStatus += "enabled, ";
|
||||
else mobileStatus += "not enabled, ";
|
||||
if (mobileConnected) mobileStatus += "connected";
|
||||
else mobileStatus += "not connected";
|
||||
statusMap.put("Mobile data:", mobileStatus);
|
||||
|
||||
// Is wifi available?
|
||||
NetworkInfo wifi = cm.getNetworkInfo(TYPE_WIFI);
|
||||
boolean wifiAvailable = wifi != null && wifi.isAvailable();
|
||||
// Is wifi enabled?
|
||||
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
|
||||
boolean wifiEnabled = wm != null &&
|
||||
wm.getWifiState() == WIFI_STATE_ENABLED;
|
||||
// Is wifi connected?
|
||||
boolean wifiConnected = wifi != null && wifi.isConnected();
|
||||
|
||||
String wifiStatus;
|
||||
if (wifiAvailable) wifiStatus = "Available, ";
|
||||
else wifiStatus = "Not available, ";
|
||||
if (wifiEnabled) wifiStatus += "enabled, ";
|
||||
else wifiStatus += "not enabled, ";
|
||||
if (wifiConnected) wifiStatus += "connected";
|
||||
else wifiStatus += "not connected";
|
||||
if (wm != null) {
|
||||
WifiInfo wifiInfo = wm.getConnectionInfo();
|
||||
if (wifiInfo != null) {
|
||||
int ip = wifiInfo.getIpAddress(); // Nice API, Google
|
||||
int ip1 = ip & 0xFF;
|
||||
int ip2 = (ip >> 8) & 0xFF;
|
||||
int ip3 = (ip >> 16) & 0xFF;
|
||||
int ip4 = (ip >> 24) & 0xFF;
|
||||
String address = ip1 + "." + ip2 + "." + ip3 + "." + ip4;
|
||||
wifiStatus += "\nAddress: " + address;
|
||||
}
|
||||
}
|
||||
statusMap.put("Wi-Fi:", wifiStatus);
|
||||
|
||||
// Is Bluetooth available?
|
||||
boolean btAvailable = bt != null;
|
||||
// Is Bluetooth enabled?
|
||||
boolean btEnabled = bt != null && bt.isEnabled() &&
|
||||
!StringUtils.isNullOrEmpty(bt.getAddress());
|
||||
// Is Bluetooth connectable?
|
||||
boolean btConnectable = bt != null &&
|
||||
(bt.getScanMode() == SCAN_MODE_CONNECTABLE ||
|
||||
bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE);
|
||||
// Is Bluetooth discoverable?
|
||||
boolean btDiscoverable = bt != null &&
|
||||
bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||
|
||||
String btStatus;
|
||||
if (btAvailable) btStatus = "Available, ";
|
||||
else btStatus = "Not available, ";
|
||||
if (btEnabled) btStatus += "enabled, ";
|
||||
else btStatus += "not enabled, ";
|
||||
if (btConnectable) btStatus += "connectable, ";
|
||||
else btStatus += "not connectable, ";
|
||||
if (btDiscoverable) btStatus += "discoverable";
|
||||
else btStatus += "not discoverable";
|
||||
if (bt != null) btStatus += "\nAddress: " + bt.getAddress();
|
||||
try {
|
||||
String btAddr = Settings.Secure.getString(getContentResolver(),
|
||||
"bluetooth_address");
|
||||
btStatus += "\nAddress from settings: " + btAddr;
|
||||
} catch (SecurityException e) {
|
||||
btStatus += "\nCould not get address from settings";
|
||||
}
|
||||
statusMap.put("Bluetooth:", btStatus);
|
||||
|
||||
// Stack trace
|
||||
if (stack != null) statusMap.put("Stack trace:", stack);
|
||||
|
||||
// All log output from the crashed process
|
||||
if (pid != -1) {
|
||||
StringBuilder log = new StringBuilder();
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(".*\\( *" + pid + "\\).*");
|
||||
Process process = runtime.exec("logcat -d -v time *:I");
|
||||
Scanner scanner = new Scanner(process.getInputStream());
|
||||
while (scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine();
|
||||
if (pattern.matcher(line).matches()) {
|
||||
log.append(line);
|
||||
log.append('\n');
|
||||
}
|
||||
}
|
||||
scanner.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
statusMap.put("Debugging log:", log.toString());
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(statusMap);
|
||||
sendCrash(comment, userEmail);
|
||||
finish();
|
||||
}
|
||||
|
||||
private String capitalize(String s) {
|
||||
if (StringUtils.isNullOrEmpty(s)) return s;
|
||||
char first = s.charAt(0);
|
||||
if (Character.isUpperCase(first)) return s;
|
||||
return Character.toUpperCase(first) + s.substring(1);
|
||||
}
|
||||
|
||||
private void saveCrashReport() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
for (Entry<String, String> e : getStatusMap().entrySet()) {
|
||||
s.append(e.getKey());
|
||||
s.append('\n');
|
||||
s.append(e.getValue());
|
||||
s.append("\n\n");
|
||||
}
|
||||
final String crashReport = s.toString();
|
||||
try {
|
||||
reporter.encryptCrashReportToFile(
|
||||
AndroidUtils.getCrashReportDir(this), crashReport);
|
||||
Toast.makeText(this, R.string.crash_report_saved, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
finish();
|
||||
} catch (FileNotFoundException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, "Error while saving encrypted crash report",
|
||||
e);
|
||||
Toast.makeText(this, R.string.crash_report_not_saved,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
private void closeReport() {
|
||||
cancelReports();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
@@ -24,7 +25,7 @@ public class AndroidUtils {
|
||||
// Fake Bluetooth address returned by BluetoothAdapter on API 23 and later
|
||||
private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00";
|
||||
|
||||
private static final String STORED_CRASH_REPORTS = "crash-reports";
|
||||
private static final String STORED_REPORTS = "dev-reports";
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@SuppressWarnings("deprecation")
|
||||
@@ -89,7 +90,7 @@ public class AndroidUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static File getCrashReportDir(Context ctx) {
|
||||
return ctx.getDir(STORED_CRASH_REPORTS, MODE_PRIVATE);
|
||||
public static File getReportDir(Context ctx) {
|
||||
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
package org.briarproject.android.util;
|
||||
|
||||
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.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.acra.builder.ReportBuilder;
|
||||
import org.acra.builder.ReportPrimer;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
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_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 java.util.logging.Level.WARNING;
|
||||
|
||||
public class BriarReportPrimer implements ReportPrimer {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BriarReportPrimer.class.getName());
|
||||
|
||||
@Override
|
||||
public void primeReport(Context context, ReportBuilder builder) {
|
||||
// System memory
|
||||
Object o = context.getSystemService(ACTIVITY_SERVICE);
|
||||
ActivityManager am = (ActivityManager) o;
|
||||
ActivityManager.MemoryInfo mem = new ActivityManager.MemoryInfo();
|
||||
am.getMemoryInfo(mem);
|
||||
String systemMemory;
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
systemMemory = (mem.totalMem / 1024 / 1024) + " MiB total, "
|
||||
+ (mem.availMem / 1024 / 1204) + " MiB free, "
|
||||
+ (mem.threshold / 1024 / 1024) + " MiB threshold";
|
||||
} else {
|
||||
systemMemory = (mem.availMem / 1024 / 1204) + " MiB free, "
|
||||
+ (mem.threshold / 1024 / 1024) + " MiB threshold";
|
||||
}
|
||||
builder.customData("System memory", systemMemory);
|
||||
|
||||
// Virtual machine memory
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long heap = runtime.totalMemory();
|
||||
long heapFree = runtime.freeMemory();
|
||||
long heapMax = runtime.maxMemory();
|
||||
String vmMemory = (heap / 1024 / 1024) + " MiB allocated, "
|
||||
+ (heapFree / 1024 / 1024) + " MiB free, "
|
||||
+ (heapMax / 1024 / 1024) + " MiB maximum";
|
||||
builder.customData("Virtual machine memory", vmMemory);
|
||||
|
||||
// Internal storage
|
||||
File root = Environment.getRootDirectory();
|
||||
long rootTotal = root.getTotalSpace();
|
||||
long rootFree = root.getFreeSpace();
|
||||
String internal = (rootTotal / 1024 / 1024) + " MiB total, "
|
||||
+ (rootFree / 1024 / 1024) + " MiB free";
|
||||
builder.customData("Internal storage", internal);
|
||||
|
||||
// External storage (SD card)
|
||||
File sd = Environment.getExternalStorageDirectory();
|
||||
long sdTotal = sd.getTotalSpace();
|
||||
long sdFree = sd.getFreeSpace();
|
||||
String external = (sdTotal / 1024 / 1024) + " MiB total, "
|
||||
+ (sdFree / 1024 / 1024) + " MiB free";
|
||||
builder.customData("External storage", external);
|
||||
|
||||
// Is mobile data available?
|
||||
o = context.getSystemService(CONNECTIVITY_SERVICE);
|
||||
ConnectivityManager cm = (ConnectivityManager) o;
|
||||
NetworkInfo mobile = cm.getNetworkInfo(TYPE_MOBILE);
|
||||
boolean mobileAvailable = mobile != null && mobile.isAvailable();
|
||||
// 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) method.invoke(cm);
|
||||
} catch (ClassNotFoundException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (IllegalAccessException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (InvocationTargetException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
// Is mobile data connected ?
|
||||
boolean mobileConnected = mobile != null && mobile.isConnected();
|
||||
|
||||
String mobileStatus;
|
||||
if (mobileAvailable) mobileStatus = "Available, ";
|
||||
else mobileStatus = "Not available, ";
|
||||
if (mobileEnabled) mobileStatus += "enabled, ";
|
||||
else mobileStatus += "not enabled, ";
|
||||
if (mobileConnected) mobileStatus += "connected";
|
||||
else mobileStatus += "not connected";
|
||||
builder.customData("Mobile data status", mobileStatus);
|
||||
|
||||
// Is wifi available?
|
||||
NetworkInfo wifi = cm.getNetworkInfo(TYPE_WIFI);
|
||||
boolean wifiAvailable = wifi != null && wifi.isAvailable();
|
||||
// Is wifi enabled?
|
||||
WifiManager wm = (WifiManager) context.getSystemService(WIFI_SERVICE);
|
||||
boolean wifiEnabled = wm != null &&
|
||||
wm.getWifiState() == WIFI_STATE_ENABLED;
|
||||
// Is wifi connected?
|
||||
boolean wifiConnected = wifi != null && wifi.isConnected();
|
||||
|
||||
String wifiStatus;
|
||||
if (wifiAvailable) wifiStatus = "Available, ";
|
||||
else wifiStatus = "Not available, ";
|
||||
if (wifiEnabled) wifiStatus += "enabled, ";
|
||||
else wifiStatus += "not enabled, ";
|
||||
if (wifiConnected) wifiStatus += "connected";
|
||||
else wifiStatus += "not connected";
|
||||
builder.customData("Wi-Fi status", wifiStatus);
|
||||
|
||||
if (wm != null) {
|
||||
WifiInfo wifiInfo = wm.getConnectionInfo();
|
||||
if (wifiInfo != null) {
|
||||
int ip = wifiInfo.getIpAddress(); // Nice API, Google
|
||||
int ip1 = ip & 0xFF;
|
||||
int ip2 = (ip >> 8) & 0xFF;
|
||||
int ip3 = (ip >> 16) & 0xFF;
|
||||
int ip4 = (ip >> 24) & 0xFF;
|
||||
String address = ip1 + "." + ip2 + "." + ip3 + "." + ip4;
|
||||
builder.customData("Wi-Fi address", address);
|
||||
}
|
||||
}
|
||||
|
||||
// Is Bluetooth available?
|
||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||
boolean btAvailable = bt != null;
|
||||
// Is Bluetooth enabled?
|
||||
boolean btEnabled = bt != null && bt.isEnabled() &&
|
||||
!StringUtils.isNullOrEmpty(bt.getAddress());
|
||||
// Is Bluetooth connectable?
|
||||
boolean btConnectable = bt != null &&
|
||||
(bt.getScanMode() == SCAN_MODE_CONNECTABLE ||
|
||||
bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE);
|
||||
// Is Bluetooth discoverable?
|
||||
boolean btDiscoverable = bt != null &&
|
||||
bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||
|
||||
String btStatus;
|
||||
if (btAvailable) btStatus = "Available, ";
|
||||
else btStatus = "Not available, ";
|
||||
if (btEnabled) btStatus += "enabled, ";
|
||||
else btStatus += "not enabled, ";
|
||||
if (btConnectable) btStatus += "connectable, ";
|
||||
else btStatus += "not connectable, ";
|
||||
if (btDiscoverable) btStatus += "discoverable";
|
||||
else btStatus += "not discoverable";
|
||||
builder.customData("Bluetooth status", btStatus);
|
||||
|
||||
if (bt != null) builder.customData("Bluetooth address", bt.getAddress());
|
||||
String btSettingsAddr;
|
||||
try {
|
||||
btSettingsAddr = Settings.Secure.getString(context.getContentResolver(),
|
||||
"bluetooth_address");
|
||||
} catch (SecurityException e) {
|
||||
btSettingsAddr = "Could not get address from settings";
|
||||
}
|
||||
builder.customData("Bluetooth address from settings", btSettingsAddr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.briarproject.android.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.acra.ReportField;
|
||||
import org.acra.collector.CrashReportData;
|
||||
import org.acra.sender.ReportSender;
|
||||
import org.acra.sender.ReportSenderException;
|
||||
import org.acra.util.JSONReportBuilder;
|
||||
import org.briarproject.android.AndroidComponent;
|
||||
import org.briarproject.api.reporting.DevReporter;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class BriarReportSender implements ReportSender {
|
||||
|
||||
private final AndroidComponent component;
|
||||
|
||||
@Inject
|
||||
protected DevReporter reporter;
|
||||
|
||||
public BriarReportSender(AndroidComponent component) {
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(@NonNull Context context,
|
||||
@NonNull CrashReportData errorContent)
|
||||
throws ReportSenderException {
|
||||
component.inject(this);
|
||||
|
||||
String crashReport;
|
||||
try {
|
||||
crashReport = errorContent.toJSON().toString();
|
||||
} catch (JSONReportBuilder.JSONReportException e) {
|
||||
throw new ReportSenderException("Couldn't create JSON", e);
|
||||
}
|
||||
try {
|
||||
reporter.encryptCrashReportToFile(
|
||||
AndroidUtils.getReportDir(context),
|
||||
errorContent.getProperty(ReportField.REPORT_ID),
|
||||
crashReport);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new ReportSenderException("Failed to encrypt report", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.briarproject.android.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.acra.config.ACRAConfiguration;
|
||||
import org.acra.sender.ReportSender;
|
||||
import org.acra.sender.ReportSenderFactory;
|
||||
import org.briarproject.android.BriarApplication;
|
||||
|
||||
public class BriarReportSenderFactory implements ReportSenderFactory {
|
||||
@NonNull
|
||||
@Override
|
||||
public ReportSender create(@NonNull Context context,
|
||||
@NonNull ACRAConfiguration config) {
|
||||
// ACRA passes in the Application as context
|
||||
return new BriarReportSender(
|
||||
((BriarApplication) context).getApplicationComponent());
|
||||
}
|
||||
}
|
||||
@@ -366,7 +366,7 @@ class TorPlugin implements DuplexPlugin, EventHandler,
|
||||
@Override
|
||||
public void run() {
|
||||
reporter.sendCrashReports(
|
||||
AndroidUtils.getCrashReportDir(appContext), SOCKS_PORT);
|
||||
AndroidUtils.getReportDir(appContext), SOCKS_PORT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user