create crash report files in debug version

This commit is contained in:
Helium314 2023-08-19 20:36:48 +02:00
parent 707a7bf48b
commit 356e39921b
4 changed files with 140 additions and 0 deletions

View file

@ -41,6 +41,7 @@
android:protectionLevel="signature" />
<application android:label="@string/english_ime_name"
android:name="org.dslul.openboard.inputmethod.latin.App"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"

View file

@ -0,0 +1,56 @@
package org.dslul.openboard.inputmethod.latin
import android.app.Application
import android.content.Context
import android.os.Build
import java.io.File
import java.io.IOException
import java.io.PrintWriter
import java.io.StringWriter
import java.util.*
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
CrashReportExceptionHandler(applicationContext).install()
}
}
}
// basically copied from StreetComplete
private class CrashReportExceptionHandler(val appContext: Context) : Thread.UncaughtExceptionHandler {
private var defaultUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null
fun install(): Boolean {
val ueh = Thread.getDefaultUncaughtExceptionHandler()
check(ueh !is CrashReportExceptionHandler) { "May not install several CrashReportExceptionHandlers!" }
defaultUncaughtExceptionHandler = ueh
Thread.setDefaultUncaughtExceptionHandler(this)
return true
}
override fun uncaughtException(t: Thread, e: Throwable) {
val stackTrace = StringWriter()
e.printStackTrace(PrintWriter(stackTrace))
writeCrashReportToFile("""
Thread: ${t.name}
App version: ${BuildConfig.VERSION_NAME}
Device: ${Build.BRAND} ${Build.DEVICE}, Android ${Build.VERSION.RELEASE}
Locale: ${Locale.getDefault()}
Stack trace:
$stackTrace
""")
defaultUncaughtExceptionHandler!!.uncaughtException(t, e)
}
private fun writeCrashReportToFile(text: String) {
try {
val dir = appContext.getExternalFilesDir(null) ?: return
val crashReportFile = File(dir, "crash_report_${System.currentTimeMillis()}.txt")
crashReportFile.writeText(text)
} catch (ignored: IOException) {
}
}
}

View file

@ -21,6 +21,7 @@ import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* A simple class to help with removing directories recursively.
@ -68,6 +69,10 @@ public class FileUtils {
throw new IOException("could not create parent folder");
}
FileOutputStream out = new FileOutputStream(outfile);
copyStreamToOtherStream(in, out);
}
public static void copyStreamToOtherStream(InputStream in, OutputStream out) throws IOException {
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {

View file

@ -18,7 +18,9 @@ package org.dslul.openboard.inputmethod.latin.settings;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.Preference;
@ -28,12 +30,23 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import org.dslul.openboard.inputmethod.latin.BuildConfig;
import org.dslul.openboard.inputmethod.latin.R;
import org.dslul.openboard.inputmethod.latin.common.FileUtils;
import org.dslul.openboard.inputmethod.latin.utils.ApplicationUtils;
import org.dslul.openboard.inputmethod.latin.utils.FeedbackUtils;
import org.dslul.openboard.inputmethod.latin.utils.JniUtils;
import org.dslul.openboard.inputmethodcommon.InputMethodSettingsFragment;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public final class SettingsFragment extends InputMethodSettingsFragment {
// We don't care about menu grouping.
private static final int NO_MENU_GROUP = Menu.NONE;
@ -41,6 +54,9 @@ public final class SettingsFragment extends InputMethodSettingsFragment {
private static final int MENU_ABOUT = Menu.FIRST;
// The second menu item id and order.
private static final int MENU_HELP_AND_FEEDBACK = Menu.FIRST + 1;
private static final int CRASH_REPORT_REQUEST_CODE = 985287532;
// for storing crash report files, so onActivityResult can actually use them
private final ArrayList<File> crashReportFiles = new ArrayList<>();
@Override
public void onCreate(final Bundle icicle) {
@ -67,6 +83,8 @@ public final class SettingsFragment extends InputMethodSettingsFragment {
if (actionBar != null && screenTitle != null) {
actionBar.setTitle(screenTitle);
}
if (BuildConfig.DEBUG)
askAboutCrashReports();
}
@Override
@ -110,4 +128,64 @@ public final class SettingsFragment extends InputMethodSettingsFragment {
}
return Secure.getInt(activity.getContentResolver(), "user_setup_complete", 0) != 0;
}
private void askAboutCrashReports() {
// find crash report files
final File dir = getActivity().getExternalFilesDir(null);
if (dir == null) return;
// final File[] files = dir.listFiles((file, s) -> file.getName().startsWith("crash_report"));
final File[] allFiles = dir.listFiles();
if (allFiles == null) return;
crashReportFiles.clear();
for (File file : allFiles) {
if (file.getName().startsWith("crash_report"))
crashReportFiles.add(file);
}
if (crashReportFiles.isEmpty()) return;
new AlertDialog.Builder(getActivity())
.setMessage("Crash report files found")
.setPositiveButton("get", (dialogInterface, i) -> {
final Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.putExtra(Intent.EXTRA_TITLE, "crash_reports.zip");
intent.setType("application/zip");
startActivityForResult(intent, CRASH_REPORT_REQUEST_CODE);
})
.setNeutralButton("delete", (dialogInterface, i) -> {
for (File file : crashReportFiles) {
file.delete(); // don't care whether it fails, though user will complain
}
})
.setNegativeButton("ignore", null)
.show();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != Activity.RESULT_OK || data == null) return;
if (requestCode != CRASH_REPORT_REQUEST_CODE) return;
if (crashReportFiles.isEmpty()) return;
final Uri uri = data.getData();
if (uri == null) return;
final OutputStream os;
try {
os = getActivity().getContentResolver().openOutputStream(uri);
final BufferedOutputStream bos = new BufferedOutputStream(os);
final ZipOutputStream z = new ZipOutputStream(bos);
for (File file : crashReportFiles) {
FileInputStream f = new FileInputStream(file);
z.putNextEntry(new ZipEntry(file.getName()));
FileUtils.copyStreamToOtherStream(f, z);
f.close();
z.closeEntry();
}
z.close();
bos.close();
os.close();
for (File file : crashReportFiles) {
file.delete();
}
} catch (IOException ignored) { }
}
}