/*
* Copyright 2011-2013 the original author or authors.
*
* This program 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.
*
* This program 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 de.schildbach.wallet.digitalcoin.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Formatter;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
/**
* @author Andreas Schildbach
*/
public class CrashReporter
{
private static final String STACK_TRACE_FILENAME = "crash.trace";
private static final String APPLICATION_LOG_FILENAME = "crash.log";
private static File stackTraceFile;
private static File applicationLogFile;
public static void init(final File cacheDir)
{
stackTraceFile = new File(cacheDir, STACK_TRACE_FILENAME);
applicationLogFile = new File(cacheDir, APPLICATION_LOG_FILENAME);
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(Thread.getDefaultUncaughtExceptionHandler()));
}
public static boolean hasSavedReport()
{
return stackTraceFile.exists();
}
public static void appendSavedStackTrace(final Appendable report) throws IOException
{
BufferedReader stackTraceReader = null;
try
{
stackTraceReader = new BufferedReader(new FileReader(stackTraceFile));
copy(stackTraceReader, report);
}
finally
{
if (stackTraceReader != null)
stackTraceReader.close();
stackTraceFile.delete();
}
}
public static void appendSavedApplicationLog(final Appendable report) throws IOException
{
BufferedReader applicationLogReader = null;
try
{
applicationLogReader = new BufferedReader(new FileReader(applicationLogFile));
copy(applicationLogReader, report);
}
finally
{
if (applicationLogReader != null)
applicationLogReader.close();
applicationLogFile.delete();
}
}
private static void copy(final BufferedReader in, final Appendable out) throws IOException
{
while (true)
{
final String line = in.readLine();
if (line == null)
break;
out.append(line).append('\n');
}
}
public static void appendDeviceInfo(final Appendable report, final Context context) throws IOException
{
final Resources res = context.getResources();
final Configuration config = res.getConfiguration();
final ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
report.append("Date: " + new Date() + "\n");
report.append("Phone Model: " + android.os.Build.MODEL + "\n");
report.append("Android Version: " + android.os.Build.VERSION.RELEASE + "\n");
report.append("Board: " + android.os.Build.BOARD + "\n");
report.append("Brand: " + android.os.Build.BRAND + "\n");
report.append("Device: " + android.os.Build.DEVICE + "\n");
report.append("Display: " + android.os.Build.DISPLAY + "\n");
report.append("Finger Print: " + android.os.Build.FINGERPRINT + "\n");
report.append("Host: " + android.os.Build.HOST + "\n");
report.append("ID: " + android.os.Build.ID + "\n");
// report.append("Manufacturer: " + manufacturer + "\n");
report.append("Model: " + android.os.Build.MODEL + "\n");
report.append("Product: " + android.os.Build.PRODUCT + "\n");
report.append("Tags: " + android.os.Build.TAGS + "\n");
report.append("Time: " + android.os.Build.TIME + "\n");
report.append("Type: " + android.os.Build.TYPE + "\n");
report.append("User: " + android.os.Build.USER + "\n");
report.append("Configuration: " + config + "\n");
report.append("Screen Layout: size " + (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) + " long "
+ (config.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK) + "\n");
report.append("Display Metrics: " + res.getDisplayMetrics() + "\n");
report.append("Memory Class: " + activityManager.getMemoryClass() + "\n");
}
public static void appendApplicationInfo(final Appendable report, final Context context) throws IOException
{
try
{
final PackageManager pm = context.getPackageManager();
final PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
report.append("Version: " + pi.versionName + " (" + pi.versionCode + ")\n");
report.append("Package: " + pi.packageName + "\n");
report.append("Databases:");
for (final String db : context.databaseList())
report.append(" " + db);
report.append("\n");
final File filesDir = context.getFilesDir();
report.append("\nContents of FilesDir " + filesDir + ":\n");
appendDir(report, filesDir, 0);
final File cacheDir = context.getCacheDir();
report.append("\nContents of CacheDir " + cacheDir + ":\n");
appendDir(report, cacheDir, 0);
}
catch (final NameNotFoundException x)
{
throw new IOException(x.toString());
}
}
public static void appendApplicationLog(final Appendable report) throws IOException
{
Process process = null;
BufferedReader logReader = null;
try
{
// likely to throw exception on older android devices
process = Runtime.getRuntime().exec("logcat -d -v time");
logReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = logReader.readLine()) != null)
report.append(line).append('\n');
}
finally
{
if (logReader != null)
logReader.close();
if (process != null)
process.destroy();
}
}
private static void appendDir(final Appendable report, final File file, final int indent) throws IOException
{
for (int i = 0; i < indent; i++)
report.append(" - ");
final Formatter formatter = new Formatter(report);
formatter.format("%tF %tT %8d %s\n", file.lastModified(), file.lastModified(), file.length(), file.getName());
formatter.close();
if (file.isDirectory())
for (final File f : file.listFiles())
appendDir(report, f, indent + 1);
}
private static class ExceptionHandler implements Thread.UncaughtExceptionHandler
{
private final Thread.UncaughtExceptionHandler previousHandler;
public ExceptionHandler(final Thread.UncaughtExceptionHandler previousHandler)
{
this.previousHandler = previousHandler;
}
public synchronized void uncaughtException(final Thread t, final Throwable exception)
{
try
{
saveStacktrace(exception);
saveApplicationLog();
}
catch (final IOException x)
{
x.printStackTrace();
}
previousHandler.uncaughtException(t, exception);
}
private void saveStacktrace(final Throwable exception) throws IOException
{
final PrintWriter writer = new PrintWriter(new FileWriter(stackTraceFile));
exception.printStackTrace(writer);
// If the exception was thrown in a background thread inside
// AsyncTask, then the actual exception can be found with getCause
Throwable cause = exception.getCause();
while (cause != null)
{
writer.println("\nCause:\n");
cause.printStackTrace(writer);
cause = cause.getCause();
}
writer.close();
}
private void saveApplicationLog() throws IOException
{
final PrintWriter writer = new PrintWriter(new FileWriter(applicationLogFile));
appendApplicationLog(writer);
writer.close();
}
}
}