diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..a4763d1 --- /dev/null +++ b/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.gitignore b/.gitignore index 6a165b7..a334ecf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ _site MANIFEST.MF build build.num +bin +gen *.jar diff --git a/.project b/.project new file mode 100644 index 0000000..6e04ac4 --- /dev/null +++ b/.project @@ -0,0 +1,33 @@ + + + errbit-android + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..b080d2d --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..7415177 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file diff --git a/proguard-project.txt b/proguard-project.txt new file mode 100644 index 0000000..f2fe155 --- /dev/null +++ b/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/project.properties b/project.properties new file mode 100644 index 0000000..484dab0 --- /dev/null +++ b/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-17 +android.library=true diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml new file mode 100644 index 0000000..541752f --- /dev/null +++ b/res/values-v11/styles.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/res/values-v14/styles.xml b/res/values-v14/styles.xml new file mode 100644 index 0000000..f20e015 --- /dev/null +++ b/res/values-v14/styles.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..e5f8fdc --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/res/values/styles.xml b/res/values/styles.xml new file mode 100644 index 0000000..4a10ca4 --- /dev/null +++ b/res/values/styles.xml @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/com/welaika/android/errbit/ErrbitNotifier.java b/src/com/welaika/android/errbit/ErrbitNotifier.java index 7e5ca76..648be24 100644 --- a/src/com/welaika/android/errbit/ErrbitNotifier.java +++ b/src/com/welaika/android/errbit/ErrbitNotifier.java @@ -20,412 +20,532 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -*/ + */ package com.welaika.android.errbit; -import java.net.HttpURLConnection; -import java.net.URL; - import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.lang.Thread.UncaughtExceptionHandler; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; import java.util.Random; import org.xmlpull.v1.XmlSerializer; import android.content.Context; -import android.content.pm.PackageManager; import android.content.pm.PackageInfo; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; import android.os.AsyncTask; import android.util.Log; import android.util.Xml; /** * ErrBit Notifier - * + * * Logs exceptions to Errbit * - * Based on https://github.com/loopj/airbrake-android/ v.1.3.0 + * Based on https://github.com/loopj/airbrake-android/ v.1.3.0 */ public class ErrbitNotifier { - private static final String LOG_TAG = "ErrbitNotifier"; - - private static final String AIRBRAKE_API_VERSION = "2.0"; - - private static final String NOTIFIER_NAME = "Android Errbit Notifier"; - private static final String NOTIFIER_VERSION = "0.2.0"; - private static final String NOTIFIER_URL = "http://welaika.com"; - - private static final String UNSENT_EXCEPTION_PATH = "/unsent_errbit_exceptions/"; - - private static final String ENVIRONMENT_PRODUCTION = "production"; - private static final String ENVIRONMENT_DEFAULT = ENVIRONMENT_PRODUCTION; - - // Exception meta-data - private static String environmentName = ENVIRONMENT_DEFAULT; - private static String packageName = "unknown"; - private static String versionName = "unknown"; - private static String phoneModel = android.os.Build.MODEL; - private static String androidVersion = android.os.Build.VERSION.RELEASE; - private static String brandDevice = android.os.Build.BRAND; - private static String manufacturerDevice = android.os.Build.MANUFACTURER; - - - // Anything extra the app wants to add - private static Map extraData; - - // Errbit api key - private static String apiKey; - - // Errbit api key - private static String errbit_endpoint = "http://airbrakeapp.com/notifier_api/v2/notices"; - - // Exception storage info - private static boolean notifyOnlyProduction = false; - private static String filePath; - private static boolean diskStorageEnabled = false; - - // Wrapper class to send uncaught exceptions to errbit - private static class ErrbitExceptionHandler implements UncaughtExceptionHandler { - private UncaughtExceptionHandler defaultExceptionHandler; - - public ErrbitExceptionHandler(UncaughtExceptionHandler defaultExceptionHandlerIn) { - defaultExceptionHandler = defaultExceptionHandlerIn; - } - - public void uncaughtException(Thread t, Throwable e) { - ErrbitNotifier.notify(e); - defaultExceptionHandler.uncaughtException(t, e); - } - } - - // Register to send exceptions to airbrake - public static void register(Context context, String endpoint, String apiKey) { - register(context, endpoint, apiKey, ENVIRONMENT_DEFAULT, true); - } - - public static void register(Context context, String endpoint, String apiKey, String environmentName) { - register(context, endpoint, apiKey, environmentName, true); - } - - public static void register(Context context, String endpoint, String apiKey, String environmentName, boolean notifyOnlyProduction) { - // Require an airbrake api key - if(apiKey != null) { - ErrbitNotifier.apiKey = apiKey; - } else { - throw new RuntimeException("ErrBitNotifier requires an API key."); - } - - if(endpoint != null) { - ErrbitNotifier.errbit_endpoint = "http://" + endpoint + "/notifier_api/v2/notices"; - } else { - ErrbitNotifier.errbit_endpoint = "http://airbrakeapp.com/notifier_api/v2/notices"; - } - - // Checked if context is passed - if(context == null) { - throw new IllegalArgumentException("context cannot be null."); - } - - // Fill in environment name if passed - if(environmentName != null) { - ErrbitNotifier.environmentName = environmentName; - } - - // Check which exception types to notify - ErrbitNotifier.notifyOnlyProduction = notifyOnlyProduction; - - // Connect our default exception handler - UncaughtExceptionHandler currentHandler = Thread.getDefaultUncaughtExceptionHandler(); - if(!(currentHandler instanceof ErrbitExceptionHandler) && (environmentName.equals(ENVIRONMENT_PRODUCTION) || !notifyOnlyProduction)) { - Thread.setDefaultUncaughtExceptionHandler(new ErrbitExceptionHandler(currentHandler)); - } - - // Load up current package name and version - try { - packageName = context.getPackageName(); - PackageInfo pi = context.getPackageManager().getPackageInfo(packageName, 0); - if(pi.versionName != null) { - versionName = pi.versionName; - } - } catch (Exception e) {} - - // Prepare the file storage location - // TODO: Does this need to be done in a background thread? - filePath = context.getFilesDir().getAbsolutePath() + UNSENT_EXCEPTION_PATH; - File outFile = new File(filePath); - outFile.mkdirs(); - diskStorageEnabled = outFile.exists(); - - Log.d(LOG_TAG, "Registered and ready to handle exceptions."); - - // Flush any existing exception info - new AsyncTask () { - protected Void doInBackground(Void... voi) { - flushExceptions(); - return null; - } - }.execute(); - } - - /** - * Add a custom set of key/value data that will be sent as session data with each notification - * @param extraData a Map of String -> String - */ - public static void setExtraData(Map extraData) { - ErrbitNotifier.extraData = extraData; - } - - // Fire an exception to airbrake manually - public static void notify(final Throwable e) { - notify(e, null); - } - - public static void notify(final Throwable e, final Map metaData) { - if(e != null && diskStorageEnabled) { - new AsyncTask () { - protected Void doInBackground(Void... voi) { - writeExceptionToDisk(e, metaData); - flushExceptions(); - return null; - } - }.execute(); - } - } - - private static void writeExceptionToDisk(Throwable e, final Map metaData) { - try { - // Set up the output stream - int random = new Random().nextInt(99999); - String filename = filePath + versionName + "-" + String.valueOf(random) + ".xml"; - BufferedWriter writer = new BufferedWriter(new FileWriter(filename)); - XmlSerializer s = Xml.newSerializer(); - s.setOutput(writer); - - // Start ridiculous xml building - s.startDocument("UTF-8", true); - - // Top level tag - s.startTag("", "notice"); - s.attribute("", "version", AIRBRAKE_API_VERSION); - - // Fill in the api key - s.startTag("", "api-key"); - s.text(apiKey); - s.endTag("", "api-key"); - - // Fill in the notifier data - s.startTag("", "notifier"); - s.startTag("", "name"); - s.text(NOTIFIER_NAME); - s.endTag("", "name"); - s.startTag("", "version"); - s.text(NOTIFIER_VERSION); - s.endTag("", "version"); - s.startTag("", "url"); - s.text(NOTIFIER_URL); - s.endTag("", "url"); - s.endTag("", "notifier"); - - // Fill in the error info - s.startTag("", "error"); - s.startTag("", "class"); - s.text(e.getClass().getName()); - s.endTag("", "class"); - s.startTag("", "message"); - s.text("[" + versionName + "] " + e.toString()); - s.endTag("", "message"); - - // Extract the stack traces - s.startTag("", "backtrace"); - Throwable currentEx = e; - while(currentEx != null) { - // Catch some inner exceptions without discarding the entire report - try { - StackTraceElement[] stackTrace = currentEx.getStackTrace(); - for(StackTraceElement el : stackTrace) { - s.startTag("", "line"); - try{ - s.attribute("", "method", el.getClassName() + "." + el.getMethodName()); - s.attribute("", "file", el.getFileName() == null ? "Unknown" : el.getFileName()); - s.attribute("", "number", String.valueOf(el.getLineNumber())); - }catch(Throwable ex){ - Log.v(LOG_TAG, "Exception caught:",ex); - } - s.endTag("", "line"); - } - - currentEx = currentEx.getCause(); - if(currentEx != null) { - s.startTag("", "line"); - try{ - s.attribute("", "file", "### CAUSED BY ###: " + currentEx.toString()); - s.attribute("", "number", ""); - }catch(Throwable ex){ - Log.v(LOG_TAG, "Exception caught:",ex); - } - s.endTag("", "line"); - } - } catch(Throwable innerException) { - Log.v(LOG_TAG, "Exception caught:",e); - break; - } - } - s.endTag("", "backtrace"); - s.endTag("", "error"); - - // Additional request info - s.startTag("", "request"); - - s.startTag("", "url"); - s.endTag("", "url"); - s.startTag("", "component"); - s.endTag("", "component"); - s.startTag("", "action"); - s.endTag("", "action"); - s.startTag("", "cgi-data"); - - s.startTag("", "var"); - s.attribute("", "key", "Manufacturer"); - s.text(manufacturerDevice); - s.endTag("", "var"); - - s.startTag("", "var"); - s.attribute("", "key", "Device"); - s.text(phoneModel); - s.endTag("", "var"); - - s.startTag("", "var"); - s.attribute("", "key", "Brand"); - s.text(brandDevice); - s.endTag("", "var"); - - s.startTag("", "var"); - s.attribute("", "key", "Android Version"); - s.text(androidVersion); - s.endTag("", "var"); - - s.startTag("", "var"); - s.attribute("", "key", "App Version"); - s.text(versionName); - s.endTag("", "var"); - - // Extra info, if present - if (extraData != null && !extraData.isEmpty()) { - for (Map.Entry extra : extraData.entrySet()) { - s.startTag("", "var"); - s.attribute("", "key", extra.getKey()); - s.text(extra.getValue()); - s.endTag("", "var"); - } - } - - // Metadata, if present - if (metaData != null && !metaData.isEmpty()) { - for (Map.Entry extra : metaData.entrySet()) { - s.startTag("", "var"); - s.attribute("", "key", extra.getKey()); - s.text(extra.getValue()); - s.endTag("", "var"); - } - } - - s.endTag("", "cgi-data"); - s.endTag("", "request"); - - // Production/development mode flag and app version - s.startTag("", "server-environment"); - s.startTag("", "environment-name"); - s.text(environmentName); - s.endTag("", "environment-name"); - s.startTag("", "app-version"); - s.text(versionName); - s.endTag("", "app-version"); - s.endTag("", "server-environment"); - - // Close document - s.endTag("", "notice"); - s.endDocument(); - - // Flush to disk - writer.flush(); - writer.close(); - - Log.d(LOG_TAG, "Writing new " + e.getClass().getName() + " exception to disk."); - } catch (Exception ex) { - Log.v(LOG_TAG, "Exception caught:",ex); - } - } - - private static void sendExceptionData(File file) { - try { - boolean sent = false; - URL url = new URL(errbit_endpoint); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - try { - // Set up the connection - conn.setDoOutput(true); - conn.setRequestProperty("Content-Type", "text/xml; charset=utf-8"); - - // Read from the file and send it - FileInputStream is = new FileInputStream(file); - OutputStream os = conn.getOutputStream(); - byte[] buffer = new byte[4096]; - int numRead; - while((numRead = is.read(buffer)) >= 0) { - os.write(buffer, 0, numRead); - } - os.flush(); - os.close(); - is.close(); - - // Flush the request through - int response = conn.getResponseCode(); - Log.d(LOG_TAG, "Sent exception file " + file.getName() + " to Errbit. Got response code " + String.valueOf(response)); - - sent = true; - - } catch(IOException e) { - - Log.v(LOG_TAG, "Exception caught:",e); - - } finally { - - // delete the file only if sending was successful - if ( sent ) { - file.delete(); - } - - conn.disconnect(); - } - - } catch(Exception e) { - - // unknown exception - Log.v(LOG_TAG, "Exception caught:",e); - - } - } - - private static synchronized void flushExceptions() { - File exceptionDir = new File(filePath); - if(exceptionDir.exists() && exceptionDir.isDirectory()) { - File[] exceptions = exceptionDir.listFiles(); - for(File f : exceptions) { - if(f.exists() && f.isFile()) { - sendExceptionData(f); - } - } - } - } + private static final String LOG_TAG = "ErrbitNotifier"; + + private static final String AIRBRAKE_API_VERSION = "2.0"; + + private static final String NOTIFIER_NAME = "Android Errbit Notifier"; + private static final String NOTIFIER_VERSION = "0.2.0"; + private static final String NOTIFIER_URL = "http://welaika.com"; + + private static final String UNSENT_EXCEPTION_PATH = "/unsent_errbit_exceptions/"; + + private static final String ENVIRONMENT_PRODUCTION = "production"; + private static final String ENVIRONMENT_DEFAULT = ENVIRONMENT_PRODUCTION; + + // Exception meta-data + private static String environmentName = ENVIRONMENT_DEFAULT; + private static String packageName = "unknown"; + private static String versionName = "unknown"; + private static String phoneModel = android.os.Build.MODEL; + private static String androidVersion = android.os.Build.VERSION.RELEASE; + private static String brandDevice = android.os.Build.BRAND; + private static String manufacturerDevice = android.os.Build.MANUFACTURER; + + // Anything extra the app wants to add + private static Map environmentData; + private static Map sessionData = new HashMap(); + private static Map requestData = new HashMap(); + + private static Resources defaultResources; + + // Errbit api key + private static String apiKey; + + // Errbit api key + private static String errbit_endpoint = "http://airbrakeapp.com/notifier_api/v2/notices"; + + // Exception storage info + private static String filePath; + private static boolean diskStorageEnabled = false; + + // Wrapper class to send uncaught exceptions to errbit + private static class ErrbitExceptionHandler implements + UncaughtExceptionHandler { + private UncaughtExceptionHandler defaultExceptionHandler; + + public ErrbitExceptionHandler( + UncaughtExceptionHandler defaultExceptionHandlerIn) { + defaultExceptionHandler = defaultExceptionHandlerIn; + } + + public void uncaughtException(Thread t, Throwable e) { + ErrbitNotifier.notify(e); + defaultExceptionHandler.uncaughtException(t, e); + } + } + + // Register to send exceptions to airbrake + public static void register(Context context, String endpoint, String apiKey) { + register(context, endpoint, apiKey, ENVIRONMENT_DEFAULT, true); + } + + public static void register(Context context, String endpoint, + String apiKey, String environmentName) { + register(context, endpoint, apiKey, environmentName, true); + } + + public static void register(Context context, String endpoint, + String apiKey, String environmentName, boolean notifyOnlyProduction) { + // Require an airbrake api key + if (apiKey != null) { + ErrbitNotifier.apiKey = apiKey; + } else { + throw new RuntimeException("ErrBitNotifier requires an API key."); + } + + if (endpoint != null) { + ErrbitNotifier.errbit_endpoint = "http://" + endpoint + + "/notifier_api/v2/notices"; + } else { + ErrbitNotifier.errbit_endpoint = "http://airbrakeapp.com/notifier_api/v2/notices"; + } + + // Checked if context is passed + if (context == null) { + throw new IllegalArgumentException("context cannot be null."); + } + + // Fill in environment name if passed + if (environmentName != null) { + ErrbitNotifier.environmentName = environmentName; + } + + // Connect our default exception handler + UncaughtExceptionHandler currentHandler = Thread + .getDefaultUncaughtExceptionHandler(); + if (!(currentHandler instanceof ErrbitExceptionHandler) + && (environmentName.equals(ENVIRONMENT_PRODUCTION) || !notifyOnlyProduction)) { + Thread.setDefaultUncaughtExceptionHandler(new ErrbitExceptionHandler( + currentHandler)); + } + + // Load up current package name and version + try { + packageName = context.getPackageName(); + PackageInfo pi = context.getPackageManager().getPackageInfo( + packageName, 0); + if (pi.versionName != null) { + versionName = pi.versionName; + } + } catch (Exception e) { + } + + // Prepare the file storage location + // TODO: Does this need to be done in a background thread? + filePath = context.getFilesDir().getAbsolutePath() + + UNSENT_EXCEPTION_PATH; + File outFile = new File(filePath); + outFile.mkdirs(); + diskStorageEnabled = outFile.exists(); + + Log.d(LOG_TAG, "Registered and ready to handle exceptions."); + + // Flush any existing exception info + new AsyncTask() { + protected Void doInBackground(Void... voi) { + flushExceptions(); + return null; + } + }.execute(); + } + + /** + * Add a custom set of key/value data that will be sent as session data with + * each notification + * + * @param extraData + * a Map of String -> String + */ + public static void setEnvironmentData(Map extraData) { + ErrbitNotifier.environmentData = extraData; + } + + /** + * set specific environment value + * + * @param key + * @param value + */ + public static void storeEnvironmentValue(String key, String value) { + if (environmentData == null) { + environmentData = new HashMap(); + } + environmentData.put(key, value); + } + + /** + * Store session value + * + * @param key + * @param value + */ + public static void storeSessionValue(String key, String value) { + sessionData.put(key, value); + } + + public static void setDefaultResources(Resources res) { + defaultResources = res; + } + + /** + * Store request value + * + * @param key + * @param value + */ + public static void storeRequestValue(String key, String value) { + requestData.put(key, value); + } + + // Fire an exception to airbrake manually + public static void notify(final Throwable e) { + notify(e, null, null); + } + + public static void notify(final Throwable e, Resources res) { + notify(e, null, res); + } + + public static void notify(final Throwable e, + final Map metaData) { + notify(e, metaData, null); + } + + public static void notify(final Throwable e, + final Map metaData, final Resources res) { + if (e != null && diskStorageEnabled) { + new AsyncTask() { + protected Void doInBackground(Void... voi) { + writeExceptionToDisk(e, metaData, res); + flushExceptions(); + return null; + } + }.execute(); + } + } + + private static void writeExceptionToDisk(Throwable e, + final Map metaData, Resources res) { + try { + // Set up the output stream + int random = new Random().nextInt(99999); + String filename = filePath + versionName + "-" + + String.valueOf(random) + ".xml"; + BufferedWriter writer = new BufferedWriter(new FileWriter(filename)); + XmlSerializer s = Xml.newSerializer(); + s.setOutput(writer); + + // Start ridiculous xml building + s.startDocument("UTF-8", true); + + // Top level tag + s.startTag("", "notice"); + s.attribute("", "version", AIRBRAKE_API_VERSION); + + // Fill in the api key + s.startTag("", "api-key"); + s.text(apiKey); + s.endTag("", "api-key"); + + // Fill in the notifier data + s.startTag("", "notifier"); + s.startTag("", "name"); + s.text(NOTIFIER_NAME); + s.endTag("", "name"); + s.startTag("", "version"); + s.text(NOTIFIER_VERSION); + s.endTag("", "version"); + s.startTag("", "url"); + s.text(NOTIFIER_URL); + s.endTag("", "url"); + s.endTag("", "notifier"); + + // Fill in the error info + s.startTag("", "error"); + s.startTag("", "class"); + s.text(e.getClass().getName()); + s.endTag("", "class"); + s.startTag("", "message"); + s.text("[" + versionName + "] " + e.toString()); + s.endTag("", "message"); + + // Extract the stack traces + s.startTag("", "backtrace"); + Throwable currentEx = e; + while (currentEx != null) { + // Catch some inner exceptions without discarding the entire + // report + try { + StackTraceElement[] stackTrace = currentEx.getStackTrace(); + for (StackTraceElement el : stackTrace) { + s.startTag("", "line"); + try { + s.attribute("", "method", el.getClassName() + "." + + el.getMethodName()); + s.attribute( + "", + "file", + el.getFileName() == null ? "Unknown" : el + .getFileName()); + s.attribute("", "number", + String.valueOf(el.getLineNumber())); + } catch (Throwable ex) { + Log.v(LOG_TAG, "Exception caught:", ex); + } + s.endTag("", "line"); + } + + currentEx = currentEx.getCause(); + if (currentEx != null) { + s.startTag("", "line"); + try { + s.attribute("", "file", "### CAUSED BY ###: " + + currentEx.toString()); + s.attribute("", "number", ""); + } catch (Throwable ex) { + Log.v(LOG_TAG, "Exception caught:", ex); + } + s.endTag("", "line"); + } + } catch (Throwable innerException) { + Log.v(LOG_TAG, "Exception caught:", e); + break; + } + } + s.endTag("", "backtrace"); + s.endTag("", "error"); + + // Additional request info + s.startTag("", "request"); + + s.startTag("", "url"); + s.endTag("", "url"); + s.startTag("", "component"); + s.endTag("", "component"); + s.startTag("", "action"); + s.endTag("", "action"); + + // write envirnonment data + s.startTag("", "cgi-data"); + + writeValue(s, "Manufacturer", manufacturerDevice); + writeValue(s, "Device", phoneModel); + writeValue(s, "Brand", brandDevice); + writeValue(s, "Android Version", androidVersion); + writeValue(s, "App Version", versionName); + + // use default resources instead + if (res == null || res.getConfiguration() == null) { + res = defaultResources; + } + + if (res != null && res.getConfiguration() != null) { + // write current android configurations to environment + Configuration conf = res.getConfiguration(); + // writeValue(s, "densityDpi", conf.densityDpi); + writeValue(s, "fontScale", conf.fontScale); + writeValue(s, "hardKeyboardHidden", conf.hardKeyboardHidden); + writeValue(s, "keyboard", conf.keyboard); + writeValue(s, "keyboardHidden", conf.keyboardHidden); + writeValue(s, "locale", conf.locale); + writeValue(s, "mcc", conf.mcc); + writeValue(s, "mnc", conf.mnc); + writeValue(s, "navigation", conf.navigation); + writeValue(s, "navigationHidden", conf.navigationHidden); + writeValue(s, "orientation", conf.orientation); + writeValue(s, "screenHeightDp", conf.screenHeightDp); + writeValue(s, "screenLayout", conf.screenLayout); + writeValue(s, "screenWidthDp", conf.screenWidthDp); + writeValue(s, "smallestScreenWidthDp", + conf.smallestScreenWidthDp); + writeValue(s, "touchscreen", conf.touchscreen); + writeValue(s, "uiMode", conf.uiMode); + } + + s.endTag("", "cgi-data"); + + // write session data + s.startTag("", "session"); + for (Entry entry : sessionData.entrySet()) { + writeValue(s, entry.getKey(), entry.getValue()); + } + s.endTag("", "session"); + + // write request data + s.startTag("", "params"); + + for (Entry entry : requestData.entrySet()) { + writeValue(s, entry.getKey(), entry.getValue()); + } + + // Extra info, if present + if (environmentData != null && !environmentData.isEmpty()) { + for (Map.Entry extra : environmentData + .entrySet()) { + s.startTag("", "var"); + s.attribute("", "key", extra.getKey()); + s.text(extra.getValue()); + s.endTag("", "var"); + } + } + + s.endTag("", "params"); + + // Metadata, if present + if (metaData != null && !metaData.isEmpty()) { + for (Map.Entry extra : metaData.entrySet()) { + s.startTag("", "var"); + s.attribute("", "key", extra.getKey()); + s.text(extra.getValue()); + s.endTag("", "var"); + } + } + + s.endTag("", "request"); + + // Production/development mode flag and app version + s.startTag("", "server-environment"); + s.startTag("", "environment-name"); + s.text(environmentName); + s.endTag("", "environment-name"); + s.startTag("", "app-version"); + s.text(versionName); + s.endTag("", "app-version"); + s.endTag("", "server-environment"); + + // Close document + s.endTag("", "notice"); + s.endDocument(); + + // Flush to disk + writer.flush(); + writer.close(); + + Log.d(LOG_TAG, "Writing new " + e.getClass().getName() + + " exception to disk."); + } catch (Exception ex) { + Log.v(LOG_TAG, "Exception caught:", ex); + } + } + + private static void writeValue(XmlSerializer s, String key, int value) + throws IllegalArgumentException, IllegalStateException, IOException { + writeValue(s, key, String.valueOf(value)); + } + + private static void writeValue(XmlSerializer s, String key, Object value) + throws IllegalArgumentException, IllegalStateException, IOException { + writeValue(s, key, (value == null) ? "" : value.toString()); + } + + private static void writeValue(XmlSerializer s, String key, String value) + throws IllegalArgumentException, IllegalStateException, IOException { + s.startTag("", "var"); + s.attribute("", "key", key); + s.text(value); + s.endTag("", "var"); + } + + private static void sendExceptionData(File file) { + try { + boolean sent = false; + URL url = new URL(errbit_endpoint); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + try { + // Set up the connection + conn.setDoOutput(true); + conn.setRequestProperty("Content-Type", + "text/xml; charset=utf-8"); + + // Read from the file and send it + FileInputStream is = new FileInputStream(file); + OutputStream os = conn.getOutputStream(); + byte[] buffer = new byte[4096]; + int numRead; + while ((numRead = is.read(buffer)) >= 0) { + os.write(buffer, 0, numRead); + } + os.flush(); + os.close(); + is.close(); + + // Flush the request through + int response = conn.getResponseCode(); + Log.d(LOG_TAG, + "Sent exception file " + file.getName() + + " to Errbit. Got response code " + + String.valueOf(response)); + + sent = true; + + } catch (IOException e) { + + Log.v(LOG_TAG, "Exception caught:", e); + + } finally { + + // delete the file only if sending was successful + if (sent) { + file.delete(); + } + + conn.disconnect(); + } + + } catch (Exception e) { + + // unknown exception + Log.v(LOG_TAG, "Exception caught:", e); + + } + } + + private static synchronized void flushExceptions() { + File exceptionDir = new File(filePath); + if (exceptionDir.exists() && exceptionDir.isDirectory()) { + File[] exceptions = exceptionDir.listFiles(); + for (File f : exceptions) { + if (f.exists() && f.isFile()) { + sendExceptionData(f); + } + } + } + } } \ No newline at end of file