diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml index 51811047bad8ec2d63df5bad9b09469196264fae..f101a69da0f50dac6910a40ec5e76ee4891fbcde 100644 --- a/briar-android/AndroidManifest.xml +++ b/briar-android/AndroidManifest.xml @@ -42,7 +42,7 @@ </intent-filter> </service> <activity - android:name=".android.CrashReportActivity" + android:name=".android.DevReportActivity" android:excludeFromRecents="true" android:exported="false" android:finishOnTaskLaunch="true" diff --git a/briar-android/res/layout/activity_crash.xml b/briar-android/res/layout/activity_dev_report.xml similarity index 79% rename from briar-android/res/layout/activity_crash.xml rename to briar-android/res/layout/activity_dev_report.xml index bf68dc87a261cef879cc6ac9c52b46d4e4b912cf..2570255968fbcbd375cef3ca85ec5cb751ae8e14 100644 --- a/briar-android/res/layout/activity_crash.xml +++ b/briar-android/res/layout/activity_dev_report.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> @@ -11,10 +12,11 @@ android:layout_height="wrap_content"> <TextView + android:id="@+id/title" style="@style/TextAppearance.AppCompat.Large.Inverse" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/crash_report_title"/> + tools:text="@string/crash_report_title"/> </android.support.v7.widget.Toolbar> <RelativeLayout @@ -35,8 +37,8 @@ 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"/> + android:inputType="textMultiLine|textCapSentences" + tools:hint="@string/describe_crash"/> <EditText android:id="@+id/user_email" @@ -51,13 +53,24 @@ android:inputType="textEmailAddress" android:maxLines="1"/> + <CheckBox + android:id="@+id/include_debug_report" + 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:text="@string/include_debug_report"/> + <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin_small"> <LinearLayout - android:id="@+id/crash_status" + android:id="@+id/report_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" @@ -78,10 +91,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" - android:indeterminate="true"/> + android:indeterminate="true" + android:visibility="gone"/> <android.support.design.widget.FloatingActionButton - android:id="@+id/share_crash_report" + android:id="@+id/share_dev_report" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" diff --git a/briar-android/res/layout/list_item_crash.xml b/briar-android/res/layout/list_item_crash.xml index 8fff03f8ccdb24720296ce854943404e4ba055bd..305bee5a9ef04b88d8095d9fdf87c1fac62327a5 100644 --- a/briar-android/res/layout/list_item_crash.xml +++ b/briar-android/res/layout/list_item_crash.xml @@ -1,21 +1,35 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> - <TextView - android:id="@+id/title" - android:layout_width="wrap_content" + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/margin_small" - android:textSize="@dimen/text_size_large"/> + android:orientation="horizontal"> + + <CheckBox + android:id="@+id/include_in_report" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/margin_small" + android:textSize="@dimen/text_size_large" + tools:text="Crash log entry title"/> + </LinearLayout> <TextView android:id="@+id/content" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/margin_medium"/> + android:layout_marginBottom="@dimen/margin_medium" + tools:text="Crash log entry value"/> </LinearLayout> \ No newline at end of file diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index 997d4d451d52ed15b1fc2d9fd1946339977380fa..d4d6ff74a9172d33816c2dbfe95bb5fef265e45e 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -4,11 +4,13 @@ <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="feedback_title">Feedback</string> <string name="describe_crash">Describe what happened</string> + <string name="enter_feedback">Enter your feedback</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="include_debug_report">Include debug report?</string> + <string name="could_not_load_report_data">Could not load report data.</string> + <string name="dev_report_saved">Report saved. It will be sent the next time you log into Briar.</string> <string name="ongoing_notification_title">Signed into Briar</string> <string name="ongoing_notification_text">Touch to show the dashboard.</string> <string name="setup_title">Briar Setup</string> diff --git a/briar-android/res/xml/settings_debug.xml b/briar-android/res/xml/settings_debug.xml index e977ac7f90e7fbc13ba26775262859ddef7f7d84..ebc397d5ee147247ab1f48ca63c4e2d41960ed82 100644 --- a/briar-android/res/xml/settings_debug.xml +++ b/briar-android/res/xml/settings_debug.xml @@ -14,6 +14,10 @@ </Preference> + <Preference + android:key="send_feedback" + android:title="Send feedback"/> + </PreferenceCategory> </PreferenceScreen> \ No newline at end of file diff --git a/briar-android/src/org/briarproject/android/AndroidComponent.java b/briar-android/src/org/briarproject/android/AndroidComponent.java index fcb53c41cb7f3edabd1df6394f9829fee2bf97a6..534399af844580a79d51827df8f199278ba96c1a 100644 --- a/briar-android/src/org/briarproject/android/AndroidComponent.java +++ b/briar-android/src/org/briarproject/android/AndroidComponent.java @@ -38,7 +38,7 @@ import dagger.Component; }) public interface AndroidComponent extends CoreEagerSingletons { - void inject(CrashReportActivity crashReportActivity); + void inject(DevReportActivity devReportActivity); void inject(SplashScreenActivity activity); diff --git a/briar-android/src/org/briarproject/android/BriarApplication.java b/briar-android/src/org/briarproject/android/BriarApplication.java index 688dae84bfe9837c284f47c272726bc800f48dae..eec480309deb5f4ae863345868662f0a6d4d670c 100644 --- a/briar-android/src/org/briarproject/android/BriarApplication.java +++ b/briar-android/src/org/briarproject/android/BriarApplication.java @@ -18,8 +18,8 @@ import java.util.logging.Logger; logcatArguments = {"-d", "-v", "time", "*:I"}, reportSenderFactoryClasses = {BriarReportSenderFactory.class}, mode = ReportingInteractionMode.DIALOG, - reportDialogClass = CrashReportActivity.class, - resDialogOkToast = R.string.crash_report_saved, + reportDialogClass = DevReportActivity.class, + resDialogOkToast = R.string.dev_report_saved, deleteOldUnsentReportsOnApplicationStart = false ) public class BriarApplication extends Application { diff --git a/briar-android/src/org/briarproject/android/CrashReportActivity.java b/briar-android/src/org/briarproject/android/CrashReportActivity.java deleted file mode 100644 index e93b7b53daa142818bd8fad3e770ef4745b4aa89..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/CrashReportActivity.java +++ /dev/null @@ -1,210 +0,0 @@ -package org.briarproject.android; - -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v7.app.AlertDialog; -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 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.api.reporting.DevReporter; - -import java.io.File; -import java.io.IOException; -import java.util.Map.Entry; -import java.util.logging.Logger; - -import javax.inject.Inject; - -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 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; - - boolean reviewing; - - @Override - public void onCreate(Bundle state) { - super.onCreate(state); - setContentView(R.layout.activity_crash); - - ((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( - new OnClickListener() { - @Override - public void onClick(View v) { - processReport(); - } - }); - - 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); - closeReport(); - } - - @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) - .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(); - } - - private void refresh() { - status.setVisibility(INVISIBLE); - progress.setVisibility(VISIBLE); - status.removeAllViews(); - new AsyncTask<Void, Void, CrashReportData>() { - - @Override - 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(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); - progress.setVisibility(GONE); - } - }.execute(); - } - - private void processReport() { - // Retrieve user comment - final String comment = userCommentView != null ? - userCommentView.getText().toString() : ""; - - // 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 { - userEmail = prefs.getString(ACRA.PREF_USER_EMAIL_ADDRESS, ""); - } - sendCrash(comment, userEmail); - finish(); - } - - private void closeReport() { - cancelReports(); - finish(); - } -} diff --git a/briar-android/src/org/briarproject/android/DevReportActivity.java b/briar-android/src/org/briarproject/android/DevReportActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..4c550b07f960e1ce3fdbc81affd6649dcbf8973b --- /dev/null +++ b/briar-android/src/org/briarproject/android/DevReportActivity.java @@ -0,0 +1,347 @@ +package org.briarproject.android; + +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.app.AlertDialog; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +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.UserFeedback; +import org.briarproject.api.reporting.DevReporter; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Logger; + +import javax.inject.Inject; + +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; +import static org.acra.ReportField.ANDROID_VERSION; +import static org.acra.ReportField.APP_VERSION_CODE; +import static org.acra.ReportField.APP_VERSION_NAME; +import static org.acra.ReportField.PACKAGE_NAME; +import static org.acra.ReportField.REPORT_ID; +import static org.acra.ReportField.STACK_TRACE; + +public class DevReportActivity extends BaseCrashReportDialog + implements DialogInterface.OnClickListener, + DialogInterface.OnCancelListener, + CompoundButton.OnCheckedChangeListener { + + private static final Logger LOG = + Logger.getLogger(DevReportActivity.class.getName()); + + private static final String PREF_EXCLUDED_FIELDS = "excludedReportFields"; + private static final String STATE_REVIEWING = "reviewing"; + private static final Set<ReportField> requiredFields = new HashSet<>(); + + static { + requiredFields.add(REPORT_ID); + requiredFields.add(APP_VERSION_CODE); + requiredFields.add(APP_VERSION_NAME); + requiredFields.add(PACKAGE_NAME); + requiredFields.add(ANDROID_VERSION); + requiredFields.add(STACK_TRACE); + } + + @Inject + protected DevReporter reporter; + + private SharedPreferencesFactory sharedPreferencesFactory; + private Set<ReportField> excludedFields; + private EditText userCommentView = null; + private EditText userEmailView = null; + private CheckBox includeDebugReport = null; + private LinearLayout report = null; + private View progress = null; + private View share = null; + private boolean reviewing = false; + + @Override + public void onCreate(Bundle state) { + super.onCreate(state); + setContentView(R.layout.activity_dev_report); + + ((BriarApplication) getApplication()).getApplicationComponent() + .inject(this); + + sharedPreferencesFactory = + new SharedPreferencesFactory(getApplicationContext(), + getConfig()); + + final SharedPreferences prefs = sharedPreferencesFactory.create(); + excludedFields = new HashSet<>(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + for (String name : prefs.getStringSet(PREF_EXCLUDED_FIELDS, + new HashSet<String>())) { + excludedFields.add(ReportField.valueOf(name)); + } + } + + TextView title = (TextView) findViewById(R.id.title); + userCommentView = (EditText) findViewById(R.id.user_comment); + userEmailView = (EditText) findViewById(R.id.user_email); + includeDebugReport = (CheckBox) findViewById(R.id.include_debug_report); + report = (LinearLayout) findViewById(R.id.report_content); + progress = findViewById(R.id.progress_wheel); + share = findViewById(R.id.share_dev_report); + + title.setText(isFeedback() ? R.string.feedback_title : + R.string.crash_report_title); + userCommentView.setHint(isFeedback() ? R.string.enter_feedback : + R.string.describe_crash); + + includeDebugReport.setVisibility(isFeedback() ? VISIBLE : GONE); + report.setVisibility(isFeedback() ? GONE : VISIBLE); + + includeDebugReport.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (includeDebugReport.isChecked()) + refresh(); + else + report.setVisibility(GONE); + } + }); + share.setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + processReport(); + } + }); + + 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 (!isFeedback() && !reviewing) showCrashDialog(); + if (!isFeedback() || includeDebugReport.isChecked()) refresh(); + } + + @Override + public void onSaveInstanceState(Bundle state) { + super.onSaveInstanceState(state); + state.putBoolean(STATE_REVIEWING, reviewing); + } + + @Override + public void onBackPressed() { + closeReport(); + } + + @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(); + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + ReportField field = (ReportField) buttonView.getTag(); + if (field != null) { + if (isChecked) + excludedFields.remove(field); + else + excludedFields.add(field); + } + } + + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + private boolean isFeedback() { + return getException() instanceof UserFeedback; + } + + private void showCrashDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this, + R.style.BriarDialogTheme); + 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(); + } + + private void refresh() { + report.setVisibility(INVISIBLE); + progress.setVisibility(VISIBLE); + report.removeAllViews(); + new AsyncTask<Void, Void, CrashReportData>() { + + @Override + 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(CrashReportData crashData) { + LayoutInflater inflater = getLayoutInflater(); + if (crashData != null) { + for (Entry<ReportField, String> e : crashData.entrySet()) { + ReportField field = e.getKey(); + boolean required = requiredFields.contains(field); + boolean excluded = excludedFields.contains(field); + View v = inflater.inflate(R.layout.list_item_crash, + report, false); + CheckBox cb = (CheckBox) v + .findViewById(R.id.include_in_report); + cb.setTag(field); + cb.setChecked(required || !excluded); + cb.setEnabled(!required); + cb.setOnCheckedChangeListener(DevReportActivity.this); + ((TextView) v.findViewById(R.id.title)) + .setText(e.getKey().toString()); + ((TextView) v.findViewById(R.id.content)) + .setText(e.getValue()); + report.addView(v); + } + } else { + View v = inflater.inflate( + android.R.layout.simple_list_item_1, report, false); + ((TextView) v.findViewById(android.R.id.text1)) + .setText(R.string.could_not_load_report_data); + report.addView(v); + } + report.setVisibility(VISIBLE); + progress.setVisibility(GONE); + } + }.execute(); + } + + private void processReport() { + userCommentView.setEnabled(false); + userEmailView.setEnabled(false); + share.setEnabled(false); + progress.setVisibility(VISIBLE); + final boolean includeReport = + !isFeedback() || includeDebugReport.isChecked(); + new AsyncTask<Void, Void, Boolean>() { + + @Override + protected Boolean doInBackground(Void... args) { + File reportFile = (File) getIntent().getSerializableExtra( + ACRAConstants.EXTRA_REPORT_FILE); + CrashReportPersister persister = new CrashReportPersister(); + try { + CrashReportData data = persister.load(reportFile); + if (includeReport) { + for (ReportField field : excludedFields) { + LOG.info("Removing field " + field.name()); + data.remove(field); + } + } else { + Iterator<Entry<ReportField, String>> iter = + data.entrySet().iterator(); + while (iter.hasNext()) { + Entry<ReportField, String> e = iter.next(); + if (!requiredFields.contains(e.getKey())) { + iter.remove(); + } + } + } + persister.store(data, reportFile); + return true; + } catch (IOException e) { + LOG.log(WARNING, "Error processing report file", e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean success) { + final SharedPreferences prefs = + sharedPreferencesFactory.create(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + final SharedPreferences.Editor prefEditor = + prefs.edit(); + Set<String> fields = new HashSet<>(); + for (ReportField field : excludedFields) { + fields.add(field.name()); + } + prefEditor.putStringSet(PREF_EXCLUDED_FIELDS, fields); + prefEditor.commit(); + } + + if (success) { + // Retrieve user comment + final String comment = userCommentView != null ? + userCommentView.getText().toString() : ""; + + // Store the user email + final String userEmail; + if (userEmailView != null) { + userEmail = userEmailView.getText().toString(); + final SharedPreferences.Editor prefEditor = + prefs.edit(); + prefEditor + .putString(ACRA.PREF_USER_EMAIL_ADDRESS, + userEmail); + prefEditor.commit(); + } else { + userEmail = + prefs.getString(ACRA.PREF_USER_EMAIL_ADDRESS, + ""); + } + sendCrash(comment, userEmail); + } + finish(); + } + }.execute(); + } + + private void closeReport() { + cancelReports(); + finish(); + } +} diff --git a/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java b/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java index e38eeff714232a4e8b699ddbddd05c796e9fb7db..462f0771b408357d6acb1cd4bc4b188f1a46b320 100644 --- a/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java +++ b/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java @@ -15,9 +15,11 @@ import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.ViewGroup; +import org.acra.ACRA; import org.briarproject.R; import org.briarproject.android.SettingsActivity; import org.briarproject.android.util.AndroidUtils; +import org.briarproject.android.util.UserFeedback; import org.briarproject.android.widget.PreferenceDividerDecoration; import org.briarproject.api.db.DbException; import org.briarproject.api.event.Event; @@ -131,6 +133,14 @@ public class SettingsFragment extends PreferenceFragmentCompat if (SHOW_TESTING_ACTIVITY) { addPreferencesFromResource(R.xml.settings_debug); + findPreference("send_feedback").setOnPreferenceClickListener( + new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + triggerFeedback(); + return true; + } + }); } loadSettings(); @@ -211,6 +221,16 @@ public class SettingsFragment extends PreferenceFragmentCompat }); } + private void triggerFeedback() { + new Thread(new Runnable() { + @Override + public void run() { + ACRA.getErrorReporter() + .handleException(new UserFeedback(), false); + } + }).start(); + } + @Override public boolean onPreferenceChange(Preference preference, Object o) { if (preference == enableBluetooth) { diff --git a/briar-android/src/org/briarproject/android/util/BriarReportSender.java b/briar-android/src/org/briarproject/android/util/BriarReportSender.java index 49507a6356526ca1ef5a898efbb8083ef65ba6ab..50b6fa2c54d4a1745989d8495ba4f2263f996567 100644 --- a/briar-android/src/org/briarproject/android/util/BriarReportSender.java +++ b/briar-android/src/org/briarproject/android/util/BriarReportSender.java @@ -39,7 +39,7 @@ public class BriarReportSender implements ReportSender { throw new ReportSenderException("Couldn't create JSON", e); } try { - reporter.encryptCrashReportToFile( + reporter.encryptReportToFile( AndroidUtils.getReportDir(context), errorContent.getProperty(ReportField.REPORT_ID), crashReport); diff --git a/briar-android/src/org/briarproject/android/util/UserFeedback.java b/briar-android/src/org/briarproject/android/util/UserFeedback.java new file mode 100644 index 0000000000000000000000000000000000000000..3c259458e51afe025ac0f2422e7d17d7b906a38d --- /dev/null +++ b/briar-android/src/org/briarproject/android/util/UserFeedback.java @@ -0,0 +1,5 @@ +package org.briarproject.android.util; + +public class UserFeedback extends Exception { + +} diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java index 0ba0a66604e2282faefa1f0297e9ba53d64ea4ff..043839d6f7b84ffc35a229af9653eeada9fb0329 100644 --- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java +++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java @@ -365,7 +365,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, ioExecutor.execute(new Runnable() { @Override public void run() { - reporter.sendCrashReports( + reporter.sendReports( AndroidUtils.getReportDir(appContext), SOCKS_PORT); } }); diff --git a/briar-api/src/org/briarproject/api/reporting/DevReporter.java b/briar-api/src/org/briarproject/api/reporting/DevReporter.java index 6dc5f98023587132b4a0c1c387dffae22bfc8912..f4b17820c1a46394e20ae9e9769db0adc490f17b 100644 --- a/briar-api/src/org/briarproject/api/reporting/DevReporter.java +++ b/briar-api/src/org/briarproject/api/reporting/DevReporter.java @@ -2,6 +2,7 @@ package org.briarproject.api.reporting; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; /** * A task for reporting back to the developers. @@ -9,20 +10,20 @@ import java.io.FileNotFoundException; public interface DevReporter { /** - * Store a crash report encrypted on-disk to be sent later. + * Store a report encrypted on-disk to be sent later. * - * @param crashReportDir the directory where crash reports are stored. - * @param crashReport the crash report in the form expected by the server. + * @param reportDir the directory where reports are stored. + * @param report the report in the form expected by the server. * @throws FileNotFoundException if the report could not be written. */ - void encryptCrashReportToFile(File crashReportDir, String filename, - String crashReport) throws FileNotFoundException; + void encryptReportToFile(File reportDir, String filename, + String report) throws FileNotFoundException; /** - * Send crash reports previously stored on-disk. + * Send reports previously stored on-disk. * - * @param crashReportDir the directory where crash reports are stored. - * @param socksPort the SOCKS port of a Tor client. + * @param reportDir the directory where reports are stored. + * @param socksPort the SOCKS port of a Tor client. */ - void sendCrashReports(File crashReportDir, int socksPort); + void sendReports(File reportDir, int socksPort); } diff --git a/briar-core/src/org/briarproject/reporting/DevReporterImpl.java b/briar-core/src/org/briarproject/reporting/DevReporterImpl.java index 7ca4b3b45daea6fc8299835a64244f5a894008a7..6d5c49f73a7a6917d385c10d8e535fa81a65f1a4 100644 --- a/briar-core/src/org/briarproject/reporting/DevReporterImpl.java +++ b/briar-core/src/org/briarproject/reporting/DevReporterImpl.java @@ -53,17 +53,17 @@ class DevReporterImpl implements DevReporter { } @Override - public void encryptCrashReportToFile(File crashReportDir, String filename, - String crashReport) throws FileNotFoundException { + public void encryptReportToFile(File reportDir, String filename, + String report) throws FileNotFoundException { String encryptedReport = crypto.encryptToKey(devConfig.getDevPublicKey(), - StringUtils.toUtf8(crashReport)); + StringUtils.toUtf8(report)); - File report = new File(crashReportDir, filename); + File f = new File(reportDir, filename); PrintWriter writer = null; try { writer = new PrintWriter( - new OutputStreamWriter(new FileOutputStream(report))); + new OutputStreamWriter(new FileOutputStream(f))); writer.append(encryptedReport); writer.flush(); } finally { @@ -73,10 +73,10 @@ class DevReporterImpl implements DevReporter { } @Override - public void sendCrashReports(File crashReportDir, int socksPort) { - File[] reports = crashReportDir.listFiles(); + public void sendReports(File reportDir, int socksPort) { + File[] reports = reportDir.listFiles(); if (reports == null || reports.length == 0) - return; // No crash reports to send + return; // No reports to send LOG.info("Connecting to developers"); Socket s; @@ -88,7 +88,7 @@ class DevReporterImpl implements DevReporter { return; } - LOG.info("Sending crash reports to developers"); + LOG.info("Sending reports to developers"); OutputStream output; PrintWriter writer = null; try { @@ -106,7 +106,7 @@ class DevReporterImpl implements DevReporter { writer.flush(); f.delete(); } - LOG.info("Crash reports sent"); + LOG.info("Reports sent"); } catch (IOException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, "Connection to developers failed", e);