package de.westnordost.streetcomplete.tools;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import javax.inject.Inject;
import de.westnordost.streetcomplete.ApplicationConstants;
import de.westnordost.streetcomplete.R;
import de.westnordost.streetcomplete.view.dialogs.AlertDialogBuilder;
public class CrashReportExceptionHandler implements Thread.UncaughtExceptionHandler
{
private Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler;
private final Context appCtx;
private final static String CRASHREPORT = "crashreport.txt";
private final static String ENC = "UTF-8";
private final static String MAILTO = "osm@westnordost.de";
private final static String GOOGLEPLAY = "com.android.vending";
@Inject public CrashReportExceptionHandler(Context appCtx)
{
this.appCtx = appCtx;
install();
}
private boolean install()
{
String installerPackageName = appCtx.getPackageManager().getInstallerPackageName(appCtx.getPackageName());
// developer. Don't need this functionality (it might even interfere with unit tests)
if(installerPackageName == null) return false;
// don't need this for google play users: they have their own crash reports
if(GOOGLEPLAY.equals(installerPackageName)) return false;
Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler();
if(ueh instanceof CrashReportExceptionHandler)
{
throw new IllegalStateException("May not install several CrashReportExceptionHandlers!");
}
defaultUncaughtExceptionHandler = ueh;
Thread.setDefaultUncaughtExceptionHandler(this);
return true;
}
public void askUserToSendCrashReportIfExists(Activity activityCtx)
{
if(hasCrashReport())
{
String reportText = readCrashReportFromFile();
deleteCrashReport();
askUserToSendErrorReport(activityCtx, R.string.crash_title, reportText);
}
}
public void askUserToSendErrorReport(Activity activityCtx, int titleResourceId, Exception e)
{
StringWriter stackTrace = new StringWriter();
e.printStackTrace(new PrintWriter(stackTrace));
askUserToSendErrorReport(activityCtx, titleResourceId, stackTrace.toString());
}
private void askUserToSendErrorReport(final Activity activityCtx, final int titleResourceId, String error)
{
final String report =
"Describe how to reproduce it here:\n\n\n\n" +
getDeviceInformationString() + "\n" + error;
new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run()
{
new AlertDialogBuilder(activityCtx)
.setTitle(titleResourceId)
.setMessage(R.string.crash_message)
.setPositiveButton(R.string.crash_compose_email, new DialogInterface.OnClickListener()
{
@Override public void onClick(DialogInterface dialog, int which)
{
sendEmail(activityCtx, report);
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener()
{
@Override public void onClick(DialogInterface dialog, int which)
{
Toast.makeText(activityCtx, "\uD83D\uDE22",Toast.LENGTH_SHORT).show();
}
})
.setCancelable(false)
.show();
}});
}
@Override public void uncaughtException(Thread t, Throwable e)
{
StringWriter stackTrace = new StringWriter();
e.printStackTrace(new PrintWriter(stackTrace));
writeCrashReportToFile(
getThreadString(t) +
"\nStack trace:\n" + stackTrace.toString());
defaultUncaughtExceptionHandler.uncaughtException(t, e);
}
private String getDeviceInformationString()
{
return "Device: " + Build.BRAND + " " + Build.DEVICE + ", Android " + Build.VERSION.RELEASE;
}
private String getThreadString(Thread t)
{
return "Thread: " + t.getName();
}
private void writeCrashReportToFile(String text)
{
try
{
FileOutputStream fos = appCtx.openFileOutput(CRASHREPORT, Context.MODE_PRIVATE);
fos.write(text.getBytes(ENC));
fos.close();
}
catch (IOException e) {}
}
private boolean hasCrashReport()
{
return Arrays.asList(appCtx.fileList()).contains(CRASHREPORT);
}
private String readCrashReportFromFile()
{
try
{
FileInputStream fis = appCtx.openFileInput(CRASHREPORT);
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) != -1) {
result.write(buffer, 0, length);
}
fis.close();
return result.toString(ENC);
}
catch (IOException e) {}
return null;
}
private void deleteCrashReport()
{
appCtx.deleteFile(CRASHREPORT);
}
private void sendEmail(Activity activityCtx, String text)
{
Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse("mailto:"));
intent.putExtra(Intent.EXTRA_EMAIL, new String[] {MAILTO});
intent.putExtra(Intent.EXTRA_SUBJECT, ApplicationConstants.USER_AGENT + " Error Report");
intent.putExtra(Intent.EXTRA_TEXT, text);
if (intent.resolveActivity(activityCtx.getPackageManager()) != null) {
activityCtx.startActivity(intent);
}
else
{
Toast.makeText(activityCtx, R.string.no_email_client, Toast.LENGTH_SHORT).show();
}
}
}