Migrate crash reports to ACRA

This commit is contained in:
str4d
2016-04-02 05:05:33 +00:00
parent 141d6497ef
commit f73f0aa4ab
17 changed files with 546 additions and 387 deletions

View File

@@ -2,11 +2,11 @@
<manifest
package="org.briarproject"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="11"
android:versionName="0.11">
<uses-sdk
xmlns:tools="http://schemas.android.com/tools"
android:minSdkVersion="9"
android:targetSdkVersion="22"
@@ -45,14 +45,10 @@
android:name=".android.CrashReportActivity"
android:excludeFromRecents="true"
android:exported="false"
android:finishOnTaskLaunch="true"
android:label="@string/crash_report_title"
android:launchMode="singleInstance"
android:process=":briar_error_handler"
android:taskAffinity="org.briarproject.android.CrashHandler">
<intent-filter>
<action android:name="org.briarproject.REPORT_CRASH"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
android:process=":briar_error_handler">
</activity>
<activity
android:name=".android.ExpiredActivity"

View File

@@ -35,6 +35,10 @@ dependencies {
exclude module: 'support-v4'
exclude module: 'recyclerview-v7'
}
compile ("ch.acra:acra:4.8.5") {
exclude module: 'support-v4'
exclude module: 'support-annotations'
}
compile "info.guardianproject.panic:panic:0.5"
compile "info.guardianproject.trustedintents:trustedintents:0.2"
compile "de.hdodenhof:circleimageview:2.0.0"
@@ -52,6 +56,7 @@ dependencyVerification {
'com.android.support:design:41a9cd75ca78f25df5f573db7cedf8bb66beae00c330943923ba9f3e2051736d',
'com.android.support:support-annotations:f347a35b9748a4103b39a6714a77e2100f488d623fd6268e259c177b200e9d82',
'com.android.support:recyclerview-v7:7606373da0931a1e62588335465a0e390cd676c98117edab29220317495faefd',
'ch.acra:acra:afd5b28934d5166b55f261c85685ad59e8a4ebe9ca1960906afaa8c76d8dc9eb',
'info.guardianproject.panic:panic:a7ed9439826db2e9901649892cf9afbe76f00991b768d8f4c26332d7c9406cb2',
'info.guardianproject.trustedintents:trustedintents:6221456d8821a8d974c2acf86306900237cf6afaaa94a4c9c44e161350f80f3e',
'de.hdodenhof:circleimageview:c76d936395b50705a3f98c9220c22d2599aeb9e609f559f6048975cfc1f686b8',

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
</vector>

View File

@@ -1,47 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
<android.support.v7.widget.Toolbar
style="@style/BriarToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
style="@style/TextAppearance.AppCompat.Large.Inverse"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/crash_report_title"/>
</android.support.v7.widget.Toolbar>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/crash_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="@dimen/margin_medium">
<EditText
android:id="@+id/user_comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/margin_large"
android:layout_marginLeft="@dimen/margin_large"
android:layout_marginRight="@dimen/margin_large"
android:layout_marginStart="@dimen/margin_large"
android:hint="@string/describe_crash"
android:inputType="textMultiLine|textCapSentences"/>
<EditText
android:id="@+id/user_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/margin_large"
android:layout_marginLeft="@dimen/margin_large"
android:layout_marginRight="@dimen/margin_large"
android:layout_marginStart="@dimen/margin_large"
android:layout_marginTop="@dimen/margin_small"
android:hint="@string/optional_contact_email"
android:inputType="textEmailAddress"
android:maxLines="1"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_small">
<LinearLayout
android:id="@+id/crash_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingBottom="@dimen/listitem_height_one_line_avatar"
android:paddingEnd="@dimen/margin_large"
android:paddingLeft="@dimen/margin_large"
android:paddingRight="@dimen/margin_large"
android:paddingStart="@dimen/margin_large"
android:paddingTop="@dimen/margin_small"/>
</ScrollView>
</LinearLayout>
<ProgressBar
android:id="@+id/progress_wheel"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingBottom="@dimen/listitem_height_one_line_avatar"
android:paddingEnd="@dimen/margin_large"
android:paddingLeft="@dimen/margin_large"
android:paddingRight="@dimen/margin_large"
android:paddingStart="@dimen/margin_large"
android:paddingTop="@dimen/margin_large"/>
</ScrollView>
android:layout_centerInParent="true"
android:indeterminate="true"/>
<ProgressBar
android:id="@+id/progress_wheel"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/share_crash_report"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="@dimen/margin_large"
android:layout_marginEnd="@dimen/margin_large"
android:layout_marginRight="@dimen/margin_large"
android:background="@color/briar_accent"
android:src="@drawable/social_share"
android:tint="@color/action_bar_text"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/share_crash_report"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="@dimen/margin_large"
android:layout_marginEnd="@dimen/margin_large"
android:layout_marginRight="@dimen/margin_large"
android:background="@color/briar_accent"
android:src="@drawable/social_share"
android:tint="@color/action_bar_text"/>
</RelativeLayout>
</RelativeLayout>
</LinearLayout>

View File

@@ -4,6 +4,9 @@
<string name="nav_drawer_close_description">Close the navigation drawer</string>
<string name="app_name">Briar</string>
<string name="crash_report_title">Briar Crash Report</string>
<string name="describe_crash">Describe what happened</string>
<string name="optional_contact_email">Optional contact email</string>
<string name="could_not_load_crash_data">Could not load crash data.</string>
<string name="crash_report_saved">Crash report saved. It will be sent the next time you log into Briar.</string>
<string name="crash_report_not_saved">Could not save crash report to disk.</string>
<string name="ongoing_notification_title">Signed into Briar</string>
@@ -126,6 +129,10 @@
<string name="notify_sound_setting_default">Default ringtone</string>
<string name="notify_sound_setting_disabled">None</string>
<string name="choose_ringtone_title">Choose ringtone</string>
<string name="enable_acra_setting">Enable crash reporter</string>
<string name="acra_syslog_setting">Send system logs</string>
<string name="acra_user_email_setting">Optional contact email</string>
<string name="acra_alwaysaccept_setting">Always send reports</string>
<string name="panic_app_setting_title">Panic Button App</string>
<string name="panic_app_setting_summary">No app has been set</string>
<string name="panic_app_setting_none">None</string>
@@ -177,8 +184,8 @@
<string name="dialog_message_connect_panic_app">Are you sure that you want to allow %1$s to trigger destructive panic button actions?</string>
<string name="dialog_title_welcome">Welcome to Briar</string>
<string name="dialog_welcome_message">Add a contact to start communicating securely or press the icon in the upper left corner of the screen for more options.</string>
<string name="dialog_title_share_crash_report">Send to developers?</string>
<string name="dialog_message_share_crash_report">Would you like to send this crash report to the developers? It will be stored encrypted on your device until the next time you log into Briar, and then sent securely to the developers.</string>
<string name="dialog_title_share_crash_report">Briar has crashed</string>
<string name="dialog_message_share_crash_report">Would you like to review the crash report and send it to the developers? It will be stored encrypted on your device until the next time you log into Briar, and then sent securely to the developers.</string>
<string name="dialog_button_ok">OK</string>
<string name="dialog_button_introduce">Introduce</string>
<string name="dialog_button_accept">Accept</string>

View File

@@ -67,4 +67,33 @@
</PreferenceCategory>
<PreferenceCategory
android:title="Crash reports">
<CheckBoxPreference
android:defaultValue="true"
android:key="acra.enable"
android:title="@string/enable_acra_setting"/>
<CheckBoxPreference
android:defaultValue="true"
android:dependency="acra.enable"
android:key="acra.syslog.enable"
android:title="@string/acra_syslog_setting"/>
<EditTextPreference
android:dependency="acra.enable"
android:key="acra.user.email"
android:inputType="textEmailAddress"
android:summary=""
android:title="@string/acra_user_email_setting"/>
<CheckBoxPreference
android:defaultValue="false"
android:dependency="acra.enable"
android:key="acra.alwaysaccept"
android:title="@string/acra_alwaysaccept_setting"/>
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -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);
}

View File

@@ -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))

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
});
}

View File

@@ -15,8 +15,8 @@ public interface DevReporter {
* @param crashReport the crash report in the form expected by the server.
* @throws FileNotFoundException if the report could not be written.
*/
void encryptCrashReportToFile(File crashReportDir, String crashReport)
throws FileNotFoundException;
void encryptCrashReportToFile(File crashReportDir, String filename,
String crashReport) throws FileNotFoundException;
/**
* Send crash reports previously stored on-disk.

View File

@@ -33,8 +33,6 @@ class DevReporterImpl implements DevReporter {
Logger.getLogger(DevReporterImpl.class.getName());
private static final int SOCKET_TIMEOUT = 30 * 1000; // 30 seconds
private static final String PREFIX = "briar-";
private static final String REPORT_EXT = ".report";
private static final String CRLF = "\r\n";
private CryptoComponent crypto;
@@ -55,13 +53,12 @@ class DevReporterImpl implements DevReporter {
}
@Override
public void encryptCrashReportToFile(File crashReportDir,
public void encryptCrashReportToFile(File crashReportDir, String filename,
String crashReport) throws FileNotFoundException {
String encryptedReport =
crypto.encryptToKey(devConfig.getDevPublicKey(),
StringUtils.toUtf8(crashReport));
String filename = PREFIX + System.currentTimeMillis() + REPORT_EXT;
File report = new File(crashReportDir, filename);
PrintWriter writer = null;
try {