Zip up crash and log files on Droid to email to developer

This commit is contained in:
Aaron Culliney 2019-11-29 12:41:34 -08:00
parent 310825e2cc
commit f5899e74d6
2 changed files with 158 additions and 113 deletions

View File

@ -19,14 +19,11 @@ import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import androidx.appcompat.app.AlertDialog;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import org.deadc0de.apple2ix.basic.BuildConfig;
import org.deadc0de.apple2ix.basic.R;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedWriter;
import java.io.File;
@ -34,10 +31,13 @@ import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
public class Apple2CrashHandler {
public final static String ALL_CRASH_FILE = "apple2ix_crash.zip";
public final static String javaCrashFileName = "jcrash.txt";
public static Apple2CrashHandler getInstance() {
@ -241,8 +241,8 @@ public class Apple2CrashHandler {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Could not unlink java crash : " + javaCrashFile);
}
// remove previous log file
File allCrashFile = _getCrashFile(activity);
// remove previous crash files
File allCrashFile = _getCrashFile(activity, ALL_CRASH_FILE);
Apple2Utils.writeFile(new StringBuilder(), allCrashFile);
allCrashFile.delete();
return;
@ -280,7 +280,6 @@ public class Apple2CrashHandler {
final int stereoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(activity, /*isStereo:*/true);
StringBuilder summary = new StringBuilder();
StringBuilder allCrashData = new StringBuilder();
// prepend information about this device
summary.append("BRAND: ").append(Build.BRAND).append("\n");
@ -302,8 +301,6 @@ public class Apple2CrashHandler {
// ...
}
allCrashData.append(summary);
File[] nativeCrashes = _nativeCrashFiles(activity);
if (nativeCrashes == null) {
nativeCrashes = new File[0];
@ -320,7 +317,7 @@ public class Apple2CrashHandler {
}
});
if (len > 0) {
if (nativeCrashes.length > 0) {
Apple2Utils.exposeSymbols(activity);
}
@ -333,95 +330,41 @@ public class Apple2CrashHandler {
}
});
boolean summarizedHeader = false;
// iteratively process native crashes
for (File crash : nativeCrashes) {
ArrayList<File> allCrashFiles = new ArrayList<File>();
if (nativeCrashes.length > 0) {
for (File crash : nativeCrashes) {
String crashPath = crash.getAbsolutePath();
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Processing crash : " + crashPath);
String crashPath = crash.getAbsolutePath();
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Processing crash : " + crashPath);
String processedPath = _dumpPath2ProcessedPath(crashPath);
try {
nativeProcessCrash(crashPath, processedPath); // Run Breakpad minidump_stackwalk
} catch (UnsatisfiedLinkError ule) {
/* could happen on early lifecycle crashes */
}
String processedPath = _dumpPath2ProcessedPath(crashPath);
try {
nativeProcessCrash(crashPath, processedPath); // Run Breakpad minidump_stackwalk
} catch (UnsatisfiedLinkError ule) {
/* could happen on early lifecycle crashes */
}
StringBuilder crashData = new StringBuilder();
if (!Apple2Utils.readEntireFile(new File(processedPath), crashData)) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Error processing crash : " + crashPath);
}
allCrashData.append(">>>>>>> NATIVE CRASH [").append(crashPath).append("]\n");
allCrashData.append(crashData);
summary.append("NATIVE CRASH:\n");
allCrashFiles.add(new File(processedPath));
// append succinct information about crashing thread
String[] lines = crashData.toString().split("[\\n\\r][\\n\\r]*");
for (int i = 0, j = 0; i < lines.length; i++) {
// 2 lines of minidump summary
if (i < 2) {
if (!summarizedHeader) {
summary.append(lines[i]);
summary.append("\n");
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (bar != null) {
bar.setProgress(bar.getProgress() + 1);
}
}
continue;
}
// 1 line of crashing thread and reason
if (i == 2) {
summarizedHeader = true;
summary.append(lines[i]);
summary.append("\n");
continue;
}
// whole lotta modules
if (lines[i].startsWith("Module")) {
continue;
}
// one apparently empty line
if (lines[i].matches("^[ \\t]*$")) {
continue;
}
// append crashing thread backtrace
summary.append(lines[i]);
summary.append("\n");
final int maxSummaryBacktrace = 8;
if (j++ >= maxSummaryBacktrace) {
break;
}
});
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (bar != null) {
bar.setProgress(bar.getProgress() + 1);
}
}
});
summary.append("" + nativeCrashes.length + " native crashes\n");
}
StringBuilder javaCrashData = new StringBuilder();
File javaCrashFile = _javaCrashFile(activity);
if (javaCrashFile.exists()) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Reading java crashes file");
if (!Apple2Utils.readEntireFile(javaCrashFile, javaCrashData)) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Error processing java crash : " + javaCrashFileName);
}
summary.append("Java Crash File\n");
allCrashFiles.add(javaCrashFile);
}
allCrashData.append(">>>>>>> JAVA CRASH DATA\n");
allCrashData.append(javaCrashData);
summary.append("JAVA CRASH:\n");
summary.append(javaCrashData);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
@ -431,23 +374,12 @@ public class Apple2CrashHandler {
}
});
StringBuilder jsonData = new StringBuilder();
if (Apple2Utils.readEntireFile(new File(homeDir, Apple2Preferences.PREFS_FILE), jsonData)) {
JSONObject obj = null;
try {
obj = new JSONObject(jsonData.toString());
} catch (JSONException e) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Error reading preferences : " + e);
}
if (obj != null) {
summary.append("PREFS:\n");
summary.append(obj.toString());
allCrashData.append(">>>>>>> PREFS\n");
allCrashData.append(obj.toString());
}
allCrashFiles.add(new File(homeDir, Apple2Preferences.PREFS_FILE));
if (nativeCrashes.length > 0) {
Apple2Utils.unexposeSymbols(activity);
}
Apple2Utils.unexposeSymbols(activity);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
@ -460,8 +392,18 @@ public class Apple2CrashHandler {
}
});
// Add all the log files ...
{
File[] nativeLogs = _nativeLogs(activity);
for (File logFile : nativeLogs) {
allCrashFiles.add(logFile);
}
}
File[] allCrashesAry = new File[allCrashFiles.size()];
File nativeCrashesZip = Apple2Utils.zipFiles(allCrashFiles.toArray(allCrashesAry), _getCrashFile(activity, ALL_CRASH_FILE));
// send report with all the data
_sendEmailToDeveloperWithCrashData(activity, summary, allCrashData);
_sendEmailToDeveloperWithCrashData(activity, summary, nativeCrashesZip);
}
}).start();
}
@ -527,6 +469,21 @@ public class Apple2CrashHandler {
return new File(homeDir, javaCrashFileName);
}
private File[] _nativeLogs(Apple2Activity activity) {
FilenameFilter logFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
File file = new File(dir, name);
if (file.isDirectory()) {
return false;
}
return name.startsWith("apple2ix_log");
}
};
return new File(homeDir).listFiles(logFilter);
}
private File[] _nativeCrashFiles(Apple2Activity activity) {
FilenameFilter dmpFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
@ -556,19 +513,18 @@ public class Apple2CrashHandler {
return crashPath.substring(0, crashPath.length() - 4) + ".txt";
}
private File _getCrashFile(Apple2Activity activity) {
File allCrashFile;
private File _getCrashFile(Apple2Activity activity, String fileName) {
File file;
String storageState = Environment.getExternalStorageState();
if (storageState.equals(Environment.MEDIA_MOUNTED)) {
allCrashFile = new File(Environment.getExternalStorageDirectory(), "apple2ix_crash.txt");
file = new File(Environment.getExternalStorageDirectory(), fileName);
} else {
allCrashFile = new File(Apple2Utils.getDataDir(activity), "apple2ix_crash.txt");
file = new File(Apple2Utils.getDataDir(activity), fileName);
}
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Writing all crashes to temp file : " + allCrashFile.getAbsolutePath());
return allCrashFile;
return file;
}
private void _sendEmailToDeveloperWithCrashData(Apple2Activity activity, StringBuilder summary, StringBuilder allCrashData) {
private void _sendEmailToDeveloperWithCrashData(Apple2Activity activity, StringBuilder summary, File nativeCrashesZip) {
mAlreadySentReport.set(true);
// <sigh> ... the disaster that is early Android ... there does not appear to be a reliable way to start an
@ -585,13 +541,11 @@ public class Apple2CrashHandler {
String summaryData = summary.substring(0, len);
emailIntent.putExtra(Intent.EXTRA_TEXT, "The app crashed, please help!\n\n" + summaryData);
File allCrashFile = _getCrashFile(activity);
Apple2Utils.writeFile(allCrashData, allCrashFile);
if (!allCrashFile.setReadable(true, /*ownerOnly:*/false)) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Oops, could not set file data readable!");
if (!nativeCrashesZip.setReadable(true, /*ownerOnly:*/false)) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Oops, could not set crash file data readable!");
}
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(allCrashFile));
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(nativeCrashesZip));
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "STARTING CHOOSER FOR EMAIL ...");
activity.startActivity(Intent.createChooser(emailIntent, "Send email"));

View File

@ -22,6 +22,8 @@ import android.widget.ProgressBar;
import org.deadc0de.apple2ix.basic.BuildConfig;
import org.deadc0de.apple2ix.basic.R;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
@ -35,6 +37,8 @@ import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class Apple2Utils {
@ -103,6 +107,93 @@ public class Apple2Utils {
return attempts < maxAttempts;
}
public static File zipFiles(File[] files, File zipFile) {
zipFile.delete();
ZipOutputStream out = null;
do {
try {
zipFile.createNewFile();
} catch (IOException ioe) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Could not create zipfile " + zipFile.getAbsolutePath() + " : " + ioe.getMessage());
break;
}
final int BUF_SIZ = 4096;
BufferedInputStream origin = null;
try {
out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
} catch (IOException ioe) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Could not create zip outputStream : " + ioe.getMessage());
break;
}
byte data[] = new byte[BUF_SIZ];
for (File file : files) {
FileInputStream fi = null;
try {
fi = new FileInputStream(file);
} catch (IOException ioe) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Could not create file input stream : " + ioe.getMessage());
continue;
}
origin = new BufferedInputStream(fi, BUF_SIZ);
ZipEntry entry = new ZipEntry(file.getName());
try {
out.putNextEntry(entry);
} catch (IOException ioe) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Could not put next zip entry : " + ioe.getMessage());
continue;
}
final int maxAttempts = 5;
int attempts = 0;
do {
int count;
try {
while ((count = origin.read(data, 0, BUF_SIZ)) != -1) {
out.write(data, 0, count);
}
break;
} catch (InterruptedIOException ie) {
/* EINTR, EAGAIN ... */
} catch (IOException ioe) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "Could read/write zip data : " + ioe.getMessage());
break;
}
++attempts;
} while (attempts < maxAttempts);
try {
origin.close();
} catch (IOException ioe) {
// ...
}
}
} while (false);
if (out != null) {
try {
out.close();
} catch (IOException ioe) {
// ...
}
}
if (zipFile.exists()) {
return zipFile;
}
return null;
}
public static void migrateToExternalStorage(Apple2Activity activity) {
do {