Refactor email-logs and crash handler codepaths

- Use a new activity and change to Intent.ACTION_SEND handler
    - Fixes an issue where email data was previously lost on a re-orientation event
This commit is contained in:
Aaron Culliney 2019-12-01 11:38:30 -08:00
parent 2728d44f8c
commit d63ba9538d
5 changed files with 215 additions and 67 deletions

View File

@ -43,6 +43,11 @@
android:theme="@style/Theme.AppCompat"
/>
<activity android:name="org.deadc0de.apple2ix.Apple2EmailerActivity"
android:theme="@style/Theme.AppCompat"
android:screenOrientation="portrait"
/>
<provider
android:authorities="${applicationId}.provider"
android:name="androidx.core.content.FileProvider"

View File

@ -41,7 +41,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
public class Apple2Activity extends AppCompatActivity implements Apple2DiskChooserActivity.Callback {
public class Apple2Activity extends AppCompatActivity implements Apple2DiskChooserActivity.Callback, Apple2EmailerActivity.Callback
{
private final static String TAG = "Apple2Activity";
@ -156,9 +157,8 @@ public class Apple2Activity extends AppCompatActivity implements Apple2DiskChoos
// Is there a way to persist the user orientation setting such that we launch in the previously set orientation and avoid getting multiple onCreate() onResume()?! ... Android lifecycle edge cases are so damn kludgishly annoying ...
mSwitchingToPortrait.set(switchingToPortrait);
if (!switchingToPortrait) {
Apple2CrashHandler.getInstance().checkForCrashes(this);
}
Apple2CrashHandler.getInstance().checkForCrashes(this);
boolean extperm = true;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -210,6 +210,11 @@ public class Apple2Activity extends AppCompatActivity implements Apple2DiskChoos
mDisksMenu = new Apple2DisksMenu(this);
}
@Override
public void onEmailerFinished() {
Apple2CrashHandler.getInstance().cleanCrashData(this);
}
@Override
public void onDisksChosen(DiskArgs args) {
final String name = args.name;
@ -258,9 +263,6 @@ public class Apple2Activity extends AppCompatActivity implements Apple2DiskChoos
logMessage(LogType.DEBUG, TAG, "onResume()");
showSplashScreen(/*dismissable:*/true);
if (!mSwitchingToPortrait.get()) {
Apple2CrashHandler.getInstance().checkForCrashes(this); // NOTE : needs to be called again to clean-up
}
if (mDisksMenu == null) {
break;

View File

@ -15,7 +15,6 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import androidx.appcompat.app.AlertDialog;
@ -205,32 +204,29 @@ public class Apple2CrashHandler {
}
public void checkForCrashes(final Apple2Activity activity) {
File oldCrashFile = _getCrashLogFile(activity);
if (oldCrashFile.exists()) {
oldCrashFile.delete();
}
if (!areCrashesPresent(activity)) {
return;
}
Apple2Preferences.load(activity);
if (!(boolean) Apple2Preferences.getJSONPref(Apple2SettingsMenu.SETTINGS.CRASH)) {
_cleanCrashData(activity);
cleanCrashData(activity);
return;
}
boolean previouslyRanCrashCheck = mAlreadyRanCrashCheck.getAndSet(true);
boolean previouslySentReport = mAlreadySentReport.get();
if (previouslySentReport) {
mAlreadySentReport.set(false);
// here we assume that the crash data was previously sent via email ... if not then we lost it =P
_cleanCrashData(activity);
return;
}
if (previouslyRanCrashCheck) {
// don't keep asking on return from backgrounding
return;
}
final AlertDialog crashDialog = new AlertDialog.Builder(activity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.crasher_send).setMessage(R.string.crasher_send_message).setNegativeButton(R.string.no, null).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
final AlertDialog crashDialog = new AlertDialog.Builder(activity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.crasher_send).setMessage(R.string.crasher_send_message).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
@ -240,7 +236,7 @@ public class Apple2CrashHandler {
activity.registerAndShowDialog(crashDialog);
}
public static void emailCrashesAndLogs(final Apple2Activity activity) {
public void emailCrashesAndLogs(final Apple2Activity activity) {
final Apple2SplashScreen splashScreen = activity.getSplashScreen();
if (splashScreen != null) {
splashScreen.setDismissable(false);
@ -384,13 +380,41 @@ public class Apple2CrashHandler {
}
File[] allCrashesAry = new File[allCrashFiles.size()];
File nativeCrashesZip = Apple2Utils.zipFiles(allCrashFiles.toArray(allCrashesAry), _getCrashFile(activity, ALL_CRASH_FILE));
File nativeCrashesZip = Apple2Utils.zipFiles(allCrashFiles.toArray(allCrashesAry), _getCrashLogFile(activity));
// send report with all the data
_sendEmailToDeveloperWithCrashData(activity, summary, nativeCrashesZip);
}
}).start();
}
public void cleanCrashData(Apple2Activity activity) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Cleaning up crash data ...");
File[] nativeCrashes = _nativeCrashFiles(activity);
for (File crash : nativeCrashes) {
if (!crash.delete()) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Could not unlink crash : " + crash);
}
File processed = new File(_dumpPath2ProcessedPath(crash.getAbsolutePath()));
if (!processed.delete()) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Could not unlink processed : " + processed);
}
}
File javaCrashFile = _javaCrashFile(activity);
if (!javaCrashFile.delete()) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Could not unlink java crash : " + javaCrashFile);
}
/* HACK NOTE -- don't delete the crash log file here ... possibly the email sender program still needs the link!
File allCrashFile = _getCrashLogFile(activity);
Apple2Utils.writeFile(new StringBuilder(), allCrashFile);
allCrashFile.delete();
*/
}
public void performCrash(int crashType) {
if (BuildConfig.DEBUG) {
nativePerformCrash(crashType);
@ -445,11 +469,11 @@ public class Apple2CrashHandler {
} while (attempts < maxAttempts);
}
private static File _javaCrashFile(Apple2Activity activity) {
private File _javaCrashFile(Apple2Activity activity) {
return new File(homeDir, javaCrashFileName);
}
private static File[] _nativeLogs(Apple2Activity activity) {
private File[] _nativeLogs(Apple2Activity activity) {
FilenameFilter logFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
File file = new File(dir, name);
@ -464,7 +488,7 @@ public class Apple2CrashHandler {
return new File(homeDir).listFiles(logFilter);
}
private static File[] _nativeCrashFiles(Apple2Activity activity) {
private File[] _nativeCrashFiles(Apple2Activity activity) {
FilenameFilter dmpFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
File file = new File(dir, name);
@ -489,31 +513,34 @@ public class Apple2CrashHandler {
return new File(homeDir).listFiles(dmpFilter);
}
private static String _dumpPath2ProcessedPath(String crashPath) {
private String _dumpPath2ProcessedPath(String crashPath) {
return crashPath.substring(0, crashPath.length() - 4) + ".txt";
}
private static File _getCrashFile(Apple2Activity activity, String fileName) {
private File _getCrashLogFile(Apple2Activity activity) {
File file;
String storageState = Environment.getExternalStorageState();
if (storageState.equals(Environment.MEDIA_MOUNTED)) {
file = new File(Environment.getExternalStorageDirectory(), fileName);
file = new File(Environment.getExternalStorageDirectory(), ALL_CRASH_FILE);
} else {
file = new File(Apple2Utils.getDataDir(activity), fileName);
file = new File(Apple2Utils.getDataDir(activity), ALL_CRASH_FILE);
}
return file;
}
private static void _sendEmailToDeveloperWithCrashData(Apple2Activity activity, StringBuilder summary, File nativeCrashesZip) {
mAlreadySentReport.set(true);
private void _sendEmailToDeveloperWithCrashData(Apple2Activity activity, StringBuilder summary, File nativeCrashesZip) {
final boolean alreadyChoosing = Apple2EmailerActivity.sEmailerIsEmailing.getAndSet(true);
if (alreadyChoosing) {
return;
}
// <sigh> ... the disaster that is early Android ... there does not appear to be a reliable way to start an
// email Intent to send both text and an attachment, but we make a valiant (if futile) effort to do so here.
// And the reason to send an attachment is that you trigger an android.os.TransactionTooLargeException with too
// much text data in the EXTRA_TEXT ... </sigh>
Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", "apple2ix_crash@deadcode.org"/*non-zero variant is correct endpoint at the moment*/, null));
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "A2IX Report");
Intent emailIntent = new Intent(activity, Apple2EmailerActivity.class);
emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK/* | Intent.FLAG_ACTIVITY_CLEAR_TOP */);
final int maxCharsEmail = 4096;
int len = summary.length();
@ -525,51 +552,22 @@ public class Apple2CrashHandler {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Oops, could not set crash file data readable!");
}
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(nativeCrashesZip));
emailIntent.putExtra(Apple2EmailerActivity.EXTRA_STREAM_PATH, nativeCrashesZip.getAbsolutePath());
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "STARTING CHOOSER FOR EMAIL ...");
activity.startActivity(Intent.createChooser(emailIntent, "Send email"));
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "AFTER START ACTIVITY ...");
}
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "STARTING EMAILER ACTIVITY ...");
Apple2EmailerActivity.sEmailerCallback = activity;
private static void _cleanCrashData(Apple2Activity activity) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Cleaning up crash data ...");
int idx = 0;
File[] nativeCrashes = _nativeCrashFiles(activity);
for (File crash : nativeCrashes) {
if (!crash.delete()) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Could not unlink crash : " + crash);
}
File processed = new File(_dumpPath2ProcessedPath(crash.getAbsolutePath()));
if (!processed.delete()) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Could not unlink processed : " + processed);
}
}
File javaCrashFile = _javaCrashFile(activity);
if (!javaCrashFile.delete()) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "Could not unlink java crash : " + javaCrashFile);
}
// remove previous crash files
File allCrashFile = _getCrashFile(activity, ALL_CRASH_FILE);
Apple2Utils.writeFile(new StringBuilder(), allCrashFile);
allCrashFile.delete();
activity.startActivityForResult(emailIntent, Apple2EmailerActivity.SEND_REQUEST_CODE);
}
private final static String TAG = "Apple2CrashHandler";
private final static Apple2CrashHandler sCrashHandler = new Apple2CrashHandler();
private static String homeDir;
private String homeDir;
private Thread.UncaughtExceptionHandler mDefaultExceptionHandler;
private AtomicBoolean mAlreadyRanCrashCheck = new AtomicBoolean(false);
private static AtomicBoolean mAlreadySentReport = new AtomicBoolean(false);
private static native void nativePerformCrash(int crashType); // testing
private static native void nativeProcessCrash(String crashFilePath, String crashProcessedPath);
}

View File

@ -0,0 +1,143 @@
/*
* Apple // emulator for *nix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2019 Aaron Culliney
*
*/
package org.deadc0de.apple2ix;
import android.content.Intent;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import java.util.concurrent.atomic.AtomicBoolean;
public class Apple2EmailerActivity extends AppCompatActivity {
public static final String EXTRA_STREAM_PATH = Intent.EXTRA_STREAM + "-PATH";
public static final int SEND_REQUEST_CODE = 46;
public static final AtomicBoolean sEmailerIsEmailing = new AtomicBoolean(false);
public static Callback sEmailerCallback;
public interface Callback {
void onEmailerFinished();
}
@Override
protected void onRestoreInstanceState(Bundle inState) {
super.onRestoreInstanceState(inState);
mExtraText = inState.getString(Intent.EXTRA_TEXT);
mExtraStreamPath = inState.getString(EXTRA_STREAM_PATH);
mExtraStream = inState.getParcelable(Intent.EXTRA_STREAM);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putBoolean("ran", true);
outState.putString(Intent.EXTRA_TEXT, mExtraText);
outState.putString(EXTRA_STREAM_PATH, mExtraStreamPath);
outState.putParcelable(Intent.EXTRA_STREAM, mExtraStream);
super.onSaveInstanceState(outState);
}
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
Bundle b;
{
Intent intent = getIntent();
Bundle extras = null;
if (intent != null) {
extras = intent.getExtras();
}
if (savedState != null) {
b = savedState;
} else if (extras != null) {
b = extras;
} else {
b = new Bundle();
}
}
final boolean ran = b.getBoolean("ran");
mExtraText = b.getString(Intent.EXTRA_TEXT);
mExtraStreamPath = b.getString(EXTRA_STREAM_PATH);
MediaScannerConnection.scanFile(this,
/*paths:*/new String[] { mExtraStreamPath },
/*mimeTypes:*/new String[] { "application/zip" },
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
mExtraStream = uri;
Intent emailIntent = new Intent(Intent.ACTION_SEND);
try {
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { "apple2ix_crash@deadcode.org" });
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "A2IX Report To apple2ix_crash@deadcode.org");
emailIntent.putExtra(Intent.EXTRA_TEXT, mExtraText);
emailIntent.putExtra(Intent.EXTRA_STREAM, mExtraStream);
emailIntent.setType("message/rfc822");
if (!ran) {
Apple2Activity.logMessage(Apple2Activity.LogType.DEBUG, TAG, "STARTING EMAIL ACTIVITY ...");
startActivityForResult(emailIntent, SEND_REQUEST_CODE);
}
} catch (Throwable t) {
Apple2Activity.logMessage(Apple2Activity.LogType.ERROR, TAG, "OOPS : " + t);
setResult(RESULT_CANCELED);
finish();
}
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_CANCELED) {
// Do something?
}
if (mExtraStream != null) {
int deleted = getContentResolver().delete(mExtraStream, null, null);
}
sEmailerCallback.onEmailerFinished();
setResult(RESULT_OK);
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public void finish() {
sEmailerIsEmailing.set(false);
super.finish();
}
private String mExtraText;
private String mExtraStreamPath;
private Uri mExtraStream;
private static final String TAG = "A2EmailerActivity";
}

View File

@ -373,7 +373,7 @@ public class Apple2SettingsMenu extends Apple2AbstractMenu {
@Override
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
Apple2CrashHandler.emailCrashesAndLogs(activity);
Apple2CrashHandler.getInstance().emailCrashesAndLogs(activity);
}
},
CRASH {