/*************************************************************************************** * 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 com.ichi2.anki; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Environment; import android.os.StatFs; import android.text.TextUtils; import java.io.File; import java.io.FileOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.TimeZone; import timber.log.Timber; public class CustomExceptionHandler implements Thread.UncaughtExceptionHandler { private static CustomExceptionHandler sInstance; private Thread.UncaughtExceptionHandler mPreviousHandler; private Context mCurContext; // private Random randomGenerator = new Random(); private HashMap<String, String> mInformation = new HashMap<>(20); static CustomExceptionHandler getInstance() { if (sInstance == null) { sInstance = new CustomExceptionHandler(); Timber.i("New instance of custom exception handler"); } return sInstance; } public void init(Context context) { mPreviousHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(this); mCurContext = context; } private long getAvailableInternalMemorySize() { File path = Environment.getDataDirectory(); StatFs stat = new StatFs(path.getPath()); long blockSize = stat.getBlockSize(); long availableBlocks = stat.getAvailableBlocks(); return availableBlocks * blockSize; } private long getTotalInternalMemorySize() { File path = Environment.getDataDirectory(); StatFs stat = new StatFs(path.getPath()); long blockSize = stat.getBlockSize(); long totalBlocks = stat.getBlockCount(); return totalBlocks * blockSize; } private void collectInformation() { Timber.i("collectInformation"); if (mCurContext == null) { return; } try { Timber.i("collecting information"); PackageManager pm = mCurContext.getPackageManager(); PackageInfo pi = pm.getPackageInfo(mCurContext.getPackageName(), 0); mInformation.put("VersionName", pi.versionName); // Version mInformation.put("PackageName", pi.packageName); // Package name mInformation.put("AndroidVersion", android.os.Build.VERSION.RELEASE); // Android version mInformation.put("Board", android.os.Build.BOARD); mInformation.put("Brand", android.os.Build.BRAND); mInformation.put("Device", android.os.Build.DEVICE); mInformation.put("Display", android.os.Build.DISPLAY); // mInformation.put("FingerPrint", android.os.Build.FINGERPRINT); mInformation.put("Host", android.os.Build.HOST); mInformation.put("ID", android.os.Build.ID); mInformation.put("Model", android.os.Build.MODEL); mInformation.put("Product", android.os.Build.PRODUCT); // mInformation.put("Tags", android.os.Build.TAGS); mInformation.put("Time", Long.toString(android.os.Build.TIME)); // mInformation.put("Type", android.os.Build.TYPE); // mInformation.put("User", android.os.Build.USER); mInformation.put("TotalInternalMemory", Long.toString(getTotalInternalMemorySize())); mInformation.put("AvailableInternalMemory", Long.toString(getAvailableInternalMemorySize())); mInformation.put("Locale", AnkiDroidApp.getAppResources().getConfiguration().locale.toString()); Timber.i("Information collected"); } catch (Exception e) { Timber.i(e.toString()); } } @Override public void uncaughtException(Thread t, Throwable e) { uncaughtException(t, e, null); } public void uncaughtException(Thread t, Throwable e, String origin) { uncaughtException(t, e, origin, null); } public void uncaughtException(Thread t, Throwable e, String origin, String additionalInfo) { Timber.i("uncaughtException"); collectInformation(); Date ts = new Date(); TimeZone tz = TimeZone.getDefault(); SimpleDateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US); SimpleDateFormat df2 = new SimpleDateFormat("Z", Locale.US); df1.setTimeZone(TimeZone.getTimeZone("UTC")); String reportgeneratedutc = String.format("%s", df1.format(ts)); String reportgeneratedtzoffset = String.format("%s", df2.format(ts)); String reportgeneratedtz = String.format("%s", tz.getID()); StringBuilder reportInformation = new StringBuilder(10000); reportInformation.append(String.format("reportgeneratedutc=%s\n", reportgeneratedutc)); reportInformation.append(String.format("reportgeneratedtzoffset=%s\n", reportgeneratedtzoffset)); reportInformation.append(String.format("reportgeneratedtz=%s\n", reportgeneratedtz)); if (origin != null && origin.length() > 0) { reportInformation.append(String.format("origin=%s\n", origin)); } for (String key : mInformation.keySet()) { String value = mInformation.get(key); reportInformation.append(String.format(Locale.US, "%s=%s\n", key.toLowerCase(Locale.US), value)); } if (additionalInfo != null && !TextUtils.isEmpty(additionalInfo)) { reportInformation.append(String.format("additionalinformation=%s\n", additionalInfo)); } reportInformation.append("stacktrace=\nBegin Stacktrace\n"); // Stack trace final Writer result = new StringWriter(); final PrintWriter printWriter = new PrintWriter(result); e.printStackTrace(printWriter); reportInformation.append(String.format("%s\n", result.toString())); reportInformation.append("End Stacktrace\n\nBegin Inner exceptions\n"); // Cause, inner exceptions Throwable cause = e.getCause(); while (cause != null) { cause.printStackTrace(printWriter); reportInformation.append(String.format("%s\n", result.toString())); cause = cause.getCause(); } reportInformation.append("End Inner exceptions"); printWriter.close(); Timber.i("report infomation string created"); saveReportToFile(reportInformation.toString()); if (t != null) { mPreviousHandler.uncaughtException(t, e); } } private void saveReportToFile(String reportInformation) { try { Timber.i("saveReportFile"); Date currentDate = new Date(); SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss"); String filename = String.format("ad-%s.stacktrace", formatter.format(currentDate)); Timber.i("No external storage available"); FileOutputStream trace = mCurContext.openFileOutput(filename, Context.MODE_PRIVATE); trace.write(reportInformation.getBytes()); trace.close(); Timber.i("report saved"); } catch (Exception e) { Timber.i(e.toString()); } } }