From 6c810ee7a36d202e7f302f4ef1b775217483436c Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 25 Mar 2016 13:51:57 -0400 Subject: [PATCH] Allow user to send bug report from settings screen --- .../java/org/isoron/uhabits/ui/MainTest.java | 13 ++++ .../uhabits/unit/HabitsApplicationTest.java | 47 ++++++++++++ ...layableActivity.java => BaseActivity.java} | 28 +++++++- .../org/isoron/uhabits/HabitsApplication.java | 72 +++++++++++++++++++ .../java/org/isoron/uhabits/MainActivity.java | 32 +++++++-- .../org/isoron/uhabits/ShowHabitActivity.java | 2 +- .../fragments/HabitSelectionCallback.java | 6 +- .../uhabits/fragments/ListHabitsFragment.java | 6 +- .../uhabits/fragments/SettingsFragment.java | 1 + app/src/main/res/values/constants.xml | 1 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/preferences.xml | 11 +++ 12 files changed, 209 insertions(+), 13 deletions(-) create mode 100644 app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java rename app/src/main/java/org/isoron/uhabits/{ReplayableActivity.java => BaseActivity.java} (79%) diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java index 4d2066501..0ea767757 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java @@ -112,6 +112,7 @@ public class MainTest Activity.RESULT_OK, new Intent()); intending(hasAction(equalTo(Intent.ACTION_SEND))).respondWith(okResult); + intending(hasAction(equalTo(Intent.ACTION_SENDTO))).respondWith(okResult); intending(hasAction(equalTo(Intent.ACTION_VIEW))).respondWith(okResult); skipTutorial(); @@ -343,4 +344,16 @@ public class MainTest onData(isPreferenceWithText("Export as CSV")).perform(click()); intended(hasAction(Intent.ACTION_SEND)); } + + /** + * User opens the settings and generates a bug report. + */ + @Test + public void testGenerateBugReport() + { + openActionBarOverflowOrOptionsMenu(targetContext); + onView(withText(R.string.settings)).perform(click()); + onData(isPreferenceWithText("Generate bug report")).perform(click()); + intended(hasAction(Intent.ACTION_SENDTO)); + } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java new file mode 100644 index 000000000..8bd20370f --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +package org.isoron.uhabits.unit; + +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import org.isoron.uhabits.HabitsApplication; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class HabitsApplicationTest +{ + @Test + public void getLogcat() throws IOException + { + String msg = "LOGCAT TEST"; + new RuntimeException(msg).printStackTrace(); + + String log = HabitsApplication.getLogcat(); + assertThat(log, containsString(msg)); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ReplayableActivity.java b/app/src/main/java/org/isoron/uhabits/BaseActivity.java similarity index 79% rename from app/src/main/java/org/isoron/uhabits/ReplayableActivity.java rename to app/src/main/java/org/isoron/uhabits/BaseActivity.java index e95e8441b..4c8b2b75a 100644 --- a/app/src/main/java/org/isoron/uhabits/ReplayableActivity.java +++ b/app/src/main/java/org/isoron/uhabits/BaseActivity.java @@ -29,7 +29,7 @@ import org.isoron.uhabits.commands.Command; import java.util.LinkedList; -abstract public class ReplayableActivity extends Activity +abstract public class BaseActivity extends Activity implements Thread.UncaughtExceptionHandler { private static int MAX_UNDO_LEVEL = 15; @@ -37,11 +37,16 @@ abstract public class ReplayableActivity extends Activity private LinkedList redoList; private Toast toast; + Thread.UncaughtExceptionHandler androidExceptionHandler; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(this); + undoList = new LinkedList<>(); redoList = new LinkedList<>(); } @@ -103,7 +108,7 @@ abstract public class ReplayableActivity extends Activity @Override protected void onPostExecute(Void aVoid) { - ReplayableActivity.this.onPostExecuteCommand(refreshKey); + BaseActivity.this.onPostExecuteCommand(refreshKey); BackupManager.dataChanged("org.isoron.uhabits"); } }.execute(); @@ -115,4 +120,23 @@ abstract public class ReplayableActivity extends Activity public void onPostExecuteCommand(Long refreshKey) { } + + @Override + public void uncaughtException(Thread thread, Throwable ex) + { + try + { + ex.printStackTrace(); + HabitsApplication.generateLogFile(); + } + catch(Exception e) + { + // ignored + } + + if(androidExceptionHandler != null) + androidExceptionHandler.uncaughtException(thread, ex); + else + System.exit(1); + } } diff --git a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java index accba2f7f..27a8fbf43 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java +++ b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java @@ -21,13 +21,21 @@ package org.isoron.uhabits; import android.app.Application; import android.content.Context; +import android.os.Environment; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.view.WindowManager; import com.activeandroid.ActiveAndroid; import org.isoron.uhabits.helpers.DatabaseHelper; +import org.isoron.uhabits.helpers.DateHelper; +import java.io.BufferedReader; import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; public class HabitsApplication extends Application { @@ -76,4 +84,68 @@ public class HabitsApplication extends Application ActiveAndroid.dispose(); super.onTerminate(); } + + public static String getLogcat() throws IOException + { + StringBuilder builder = new StringBuilder(); + + String[] command = new String[] { "logcat", "-d"}; + Process process = Runtime.getRuntime().exec(command); + + InputStreamReader in = new InputStreamReader(process.getInputStream()); + BufferedReader bufferedReader = new BufferedReader(in); + + String line; + while ((line = bufferedReader.readLine()) != null) + { + builder.append(line); + builder.append('\n'); + } + + return builder.toString(); + } + + public static String getDeviceInfo() + { + if(context == null) return ""; + + StringBuilder b = new StringBuilder(); + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + + b.append(String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME)); + b.append(String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE)); + b.append(String.format("OS Version: %s (%s)\n", System.getProperty("os.version"), + android.os.Build.VERSION.INCREMENTAL)); + b.append(String.format("OS API Level: %s\n", android.os.Build.VERSION.SDK)); + b.append(String.format("Device: %s\n", android.os.Build.DEVICE)); + b.append(String.format("Model (Product): %s (%s)\n", android.os.Build.MODEL, + android.os.Build.PRODUCT)); + b.append(String.format("Manufacturer: %s\n", android.os.Build.MANUFACTURER)); + b.append(String.format("Other tags: %s\n", android.os.Build.TAGS)); + b.append(String.format("Screen Width: %s\n", wm.getDefaultDisplay().getWidth())); + b.append(String.format("Screen Height: %s\n", wm.getDefaultDisplay().getHeight())); + b.append(String.format("SD Card state: %s\n\n", Environment.getExternalStorageState())); + + return b.toString(); + } + + @NonNull + public static File generateLogFile() throws IOException + { + String logcat = getLogcat(); + String deviceInfo = getDeviceInfo(); + String date = DateHelper.getBackupDateFormat().format(DateHelper.getLocalTime()); + + if(context == null) throw new RuntimeException("application context should not be null"); + File dir = DatabaseHelper.getFilesDir("Logs"); + if (dir == null) throw new IOException("log dir should not be null"); + + File logFile = new File(String.format("%s/Log %s.txt", dir.getPath(), date)); + FileWriter output = new FileWriter(logFile); + output.write(deviceInfo); + output.write(logcat); + output.close(); + + return logFile; + } } diff --git a/app/src/main/java/org/isoron/uhabits/MainActivity.java b/app/src/main/java/org/isoron/uhabits/MainActivity.java index 701bc175d..7bd02995f 100644 --- a/app/src/main/java/org/isoron/uhabits/MainActivity.java +++ b/app/src/main/java/org/isoron/uhabits/MainActivity.java @@ -19,7 +19,6 @@ package org.isoron.uhabits; -import android.Manifest; import android.appwidget.AppWidgetManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -33,8 +32,6 @@ import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.NonNull; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; import android.support.v4.content.LocalBroadcastManager; import android.view.Menu; import android.view.MenuItem; @@ -50,7 +47,10 @@ import org.isoron.uhabits.widgets.HistoryWidgetProvider; import org.isoron.uhabits.widgets.ScoreWidgetProvider; import org.isoron.uhabits.widgets.StreakWidgetProvider; -public class MainActivity extends ReplayableActivity +import java.io.File; +import java.io.IOException; + +public class MainActivity extends BaseActivity implements ListHabitsFragment.OnHabitClickListener { private ListHabitsFragment listHabitsFragment; @@ -63,6 +63,7 @@ public class MainActivity extends ReplayableActivity public static final int RESULT_IMPORT_DATA = 1; public static final int RESULT_EXPORT_CSV = 2; public static final int RESULT_EXPORT_DB = 3; + public static final int RESULT_BUG_REPORT = 4; @Override protected void onCreate(Bundle savedInstanceState) @@ -172,6 +173,29 @@ public class MainActivity extends ReplayableActivity case RESULT_EXPORT_DB: listHabitsFragment.exportDB(); break; + + case RESULT_BUG_REPORT: + generateBugReport(); + break; + } + } + + private void generateBugReport() + { + try + { + File logFile = HabitsApplication.generateLogFile(); + + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SENDTO); + intent.setData(Uri.parse(getString(R.string.bugReportURL))); + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(logFile)); + startActivity(intent); + } + catch (IOException e) + { + e.printStackTrace(); + showToast(R.string.bug_report_failed); } } diff --git a/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java b/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java index 7a269a733..f802419ea 100644 --- a/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java @@ -33,7 +33,7 @@ import android.support.v4.content.LocalBroadcastManager; import org.isoron.uhabits.fragments.ShowHabitFragment; import org.isoron.uhabits.models.Habit; -public class ShowHabitActivity extends ReplayableActivity +public class ShowHabitActivity extends BaseActivity { public Habit habit; diff --git a/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java b/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java index 1e9c6b5df..cd5031757 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java @@ -30,7 +30,7 @@ import com.android.colorpicker.ColorPickerDialog; import com.android.colorpicker.ColorPickerSwatch; import org.isoron.uhabits.R; -import org.isoron.uhabits.ReplayableActivity; +import org.isoron.uhabits.BaseActivity; import org.isoron.uhabits.commands.ArchiveHabitsCommand; import org.isoron.uhabits.commands.ChangeHabitColorCommand; import org.isoron.uhabits.commands.DeleteHabitsCommand; @@ -47,7 +47,7 @@ public class HabitSelectionCallback implements ActionMode.Callback { private HabitListLoader loader; private List selectedPositions; - private ReplayableActivity activity; + private BaseActivity activity; private Listener listener; private DialogHelper.OnSavedListener onSavedListener; private ProgressBar progressBar; @@ -57,7 +57,7 @@ public class HabitSelectionCallback implements ActionMode.Callback void onActionModeDestroyed(ActionMode mode); } - public HabitSelectionCallback(ReplayableActivity activity, HabitListLoader loader) + public HabitSelectionCallback(BaseActivity activity, HabitListLoader loader) { this.activity = activity; this.loader = loader; diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java index 4185c9b6b..d703172a7 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java @@ -50,7 +50,7 @@ import com.mobeta.android.dslv.DragSortListView; import com.mobeta.android.dslv.DragSortListView.DropListener; import org.isoron.uhabits.R; -import org.isoron.uhabits.ReplayableActivity; +import org.isoron.uhabits.BaseActivity; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.ToggleRepetitionCommand; import org.isoron.uhabits.dialogs.FilePickerDialog; @@ -88,7 +88,7 @@ public class ListHabitsFragment extends Fragment private ListHabitsHelper helper; private List selectedPositions; private OnHabitClickListener habitClickListener; - private ReplayableActivity activity; + private BaseActivity activity; private SharedPreferences prefs; private DragSortListView listView; @@ -155,7 +155,7 @@ public class ListHabitsFragment extends Fragment public void onAttach(Activity activity) { super.onAttach(activity); - this.activity = (ReplayableActivity) activity; + this.activity = (BaseActivity) activity; habitClickListener = (OnHabitClickListener) activity; prefs = PreferenceManager.getDefaultSharedPreferences(activity); diff --git a/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java index 1d3c4abb3..ffcdffd9a 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java @@ -40,6 +40,7 @@ public class SettingsFragment extends PreferenceFragment setResultOnPreferenceClick("importData", MainActivity.RESULT_IMPORT_DATA); setResultOnPreferenceClick("exportCSV", MainActivity.RESULT_EXPORT_CSV); setResultOnPreferenceClick("exportDB", MainActivity.RESULT_EXPORT_DB); + setResultOnPreferenceClick("bugReport", MainActivity.RESULT_BUG_REPORT); } private void setResultOnPreferenceClick(String key, final int result) diff --git a/app/src/main/res/values/constants.xml b/app/src/main/res/values/constants.xml index c8a2dc753..e0e9d7dc7 100644 --- a/app/src/main/res/values/constants.xml +++ b/app/src/main/res/values/constants.xml @@ -21,5 +21,6 @@ https://isoron.github.io/uhabits/faq.html market://details?id=org.isoron.uhabits mailto:isoron+habits@gmail.com?subject=Feedback%20about%20Loop%20Habit%20Tracker + mailto:isoron+habits@gmail.com?subject=Bug%20Report%20-%20Loop%20Habit%20Tracker https://github.com/iSoron/uhabits \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 394b5dd39..c6c66f843 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -148,4 +148,7 @@ Supports full backups exported by this app, as well as files generated by Tickmate, HabitBull or Rewire. See FAQ for more information. Generates files that can be opened by spreadsheet software such as Microsoft Excel or OpenOffice Calc. This file cannot be imported back. Generates a file that contains all your data. This file can be imported back. + Failed to generate bug report. + Generate bug report + Troubleshooting \ No newline at end of file diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 8999ef51b..28ef15463 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -65,6 +65,17 @@ + + + + + + +