Allow user to send bug report from settings screen

pull/77/merge
Alinson S. Xavier 10 years ago
parent 5115379fdd
commit 6c810ee7a3

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

@ -0,0 +1,47 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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));
}
}

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

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

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

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

@ -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<Integer> 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;

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

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

@ -21,5 +21,6 @@
<string name="helpURL">https://isoron.github.io/uhabits/faq.html</string>
<string name="playStoreURL">market://details?id=org.isoron.uhabits</string>
<string name="feedbackURL" formatted="false">mailto:isoron+habits@gmail.com?subject=Feedback%20about%20Loop%20Habit%20Tracker</string>
<string name="bugReportURL" formatted="false">mailto:isoron+habits@gmail.com?subject=Bug%20Report%20-%20Loop%20Habit%20Tracker</string>
<string name="sourceCodeURL">https://github.com/iSoron/uhabits</string>
</resources>

@ -148,4 +148,7 @@
<string name="import_data_summary">Supports full backups exported by this app, as well as files generated by Tickmate, HabitBull or Rewire. See FAQ for more information.</string>
<string name="export_as_csv_summary">Generates files that can be opened by spreadsheet software such as Microsoft Excel or OpenOffice Calc. This file cannot be imported back.</string>
<string name="export_full_backup_summary">Generates a file that contains all your data. This file can be imported back.</string>
<string name="bug_report_failed">Failed to generate bug report.</string>
<string name="generate_bug_report">Generate bug report</string>
<string name="troubleshooting">Troubleshooting</string>
</resources>

@ -65,6 +65,17 @@
</PreferenceCategory>
<PreferenceCategory
android:key="pref_key_debug"
android:title="@string/troubleshooting">
<Preference
android:key="bugReport"
android:title="@string/generate_bug_report">
</Preference>
</PreferenceCategory>
<PreferenceCategory
android:key="pref_key_links"
android:title="@string/links">

Loading…
Cancel
Save