package v2.simpleUi.util;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Properties;
import v2.simpleUi.M_Button;
import v2.simpleUi.M_Caption;
import v2.simpleUi.M_Checkbox;
import v2.simpleUi.M_Container;
import v2.simpleUi.M_InfoText;
import v2.simpleUi.M_TextInput;
import android.R;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
/**
* Register the {@link ErrorHandler} like this: </br>
* Thread.setDefaultUncaughtExceptionHandler(new ErrorHandler(currentActivity));
* </br></br>
*
* Or use the {@link ErrorHandler#registerNewErrorHandler(Activity)}
* method.</br></br>
*
* To add email support, call
* {@link ErrorHandler#enableEmailReports(String, String)} </br></br>
*
* And dont forget to add the ErrorReports.xml Layout file to your res/layout
* folder!</br></br>
*
* The ErrorHandler has to be registered in the AndroidManifest.xml like this:
*
* <p class=MsoNormal style='margin-bottom:0cm;margin-bottom:.0001pt;line-height: normal;mso-layout-grid-align:none;text-autospace:none'>
* <span style='font-size: 10.0pt;font-family:"Courier New";color:teal;mso-ansi
* -language:DE'><</span><span class=SpellE><span
* style='font-size:10.0pt;font-family:"Courier New";
* color:#3F7F7F;mso-ansi-language:DE'>activity</span></span><span
* style='font-size:10.0pt;font-family:"Courier New";mso-ansi-language:DE'>
* <span class=SpellE><span
* style='color:#7F007F'>android:name</span></span><span
* style='color:black'>=</span><i><span style='color:#2A00FF'>"<span
* class=SpellE>system.ErrorHandler</span>"</span></i> <span
* class=SpellE><span style='color:#7F007F'>android:process</span></span><span
* style='color:black'>=</span><i><span style='color:#2A00FF'>":<span
* class=SpellE>myexeptionprocess</span>"</span></i><o:p></o:p></span>
* </p>
*
* <p class=MsoNormal style='margin-bottom:0cm;margin-bottom:.0001pt;line-height: normal;mso-layout-grid-align:none;text-autospace:none'>
* <span style='font-size:
* 10.0pt;font-family:"Courier New";mso-ansi-language:DE'><span
* style='mso-tab-count: 1'>����� </span><span class=SpellE><span
* style='color:#7F007F'>android:taskAffinity</span></span><span
* style='color:black'>=</span><i><span style='color:#2A00FF'>"<span
* class=SpellE>tools.ErrorHandler</span>"</span></i><span style='color:
* teal'>></span><o:p></o:p></span>
* </p>
*
* <p class=MsoNormal style='margin-bottom:0cm;margin-bottom:.0001pt;line-height: normal;mso-layout-grid-align:none;text-autospace:none'>
* <span style='font-size:
* 10.0pt;font-family:"Courier New";color:black;mso-ansi-language:DE'><span
* style='mso-tab-count:1'>����� </span></span><span style='font-size:10.0pt;
* font-family:"Courier New";color:teal;mso-ansi-language :DE'><</span><span
* class=SpellE><span style='font-size:10.0pt;font-family:"Courier New";
* color:#3F7F7F;mso-ansi-language:DE'>intent</span></span><span
* style='font-size: 10.0pt;font-family:"Courier New";color:#3F7F7F;mso-ansi-
* language:DE'>-filter</span><span style='font-size:10.0pt;font-family:"Courier
* New";color:teal;mso-ansi-language: DE'>></span><span
* style='font-size:10.0pt;font-family:"Courier New";
* mso-ansi-language:DE'><o:p></o:p></span>
* </p>
*
* <p class=MsoNormal style='margin-bottom:0cm;margin-bottom:.0001pt;line-height: normal;mso-layout-grid-align:none;text-autospace:none'>
* <span style='font-size:
* 10.0pt;font-family:"Courier New";color:black;mso-ansi-language:DE'><span
* style='mso-tab-count:2'>����������� </span></span><span
* style='font-size:10.0pt;
* font-family:"Courier New";color:teal;mso-ansi-language :DE'><</span><span
* class=SpellE><span style='font-size:10.0pt;font-family:"Courier New";
* color:#3F7F7F;mso-ansi-language:DE'>category</span></span><span
* style='font-size:10.0pt;font-family:"Courier New";mso-ansi-language:DE'>
* <span class=SpellE><span
* style='color:#7F007F'>android:name</span></span><span
* style='color:black'>=</span><i><span style='color:#2A00FF'>"<span
* class=SpellE>android.intent.category.DEFAULT</span>"</span></i> <span
* style='color:teal'>/></span><o:p></o:p></span>
* </p>
*
* <p class=MsoNormal style='margin-bottom:0cm;margin-bottom:.0001pt;line-height: normal;mso-layout-grid-align:none;text-autospace:none'>
* <span style='font-size:
* 10.0pt;font-family:"Courier New";color:black;mso-ansi-language:DE'><span
* style='mso-tab-count:1'>����� </span><span style='mso-tab-count:1'>�����
* </span></span><span style='font-size:10.0pt;font-family:"Courier
* New";color:teal;mso-ansi-language: DE'><</span><span class=SpellE><span
* style='font-size:10.0pt;font-family: "Courier New";color
* :#3F7F7F;mso-ansi-language:DE'>action</span></span><span
* style='font-size:10.0pt;font-family:"Courier New";mso-ansi-language:DE'>
* <span class=SpellE><span
* style='color:#7F007F'>android:name</span></span><span
* style='color:black'>=</span><i><span style='color:#2A00FF'>"<span
* class=SpellE>android.intent.action.VIEW</span>"</span></i> <span
* style='color:teal'>/></span><o:p></o:p></span>
* </p>
*
* <p class=MsoNormal style='margin-bottom:0cm;margin-bottom:.0001pt;line-height: normal;mso-layout-grid-align:none;text-autospace:none'>
* <span style='font-size:
* 10.0pt;font-family:"Courier New";color:black;mso-ansi-language:DE'><span
* style='mso-tab-count:1'>����� </span><span style='mso-tab-count:1'>�����
* </span></span><span style='font-size:10.0pt;font-family:"Courier
* New";color:teal;mso-ansi-language: DE'><</span><span class=SpellE><span
* style='font-size:10.0pt;font-family:
* "Courier New";color:#3F7F7F;mso-ansi-language:DE'>data</span></span><span
* style='font-size:10.0pt;font-family:"Courier New";mso-ansi-language:DE'>
* <span class=SpellE><span
* style='color:#7F007F'>android:mimeType</span></span><span
* style='color:black'>=</span><i><span style='color:#2A00FF'>"<span
* class=SpellE>errors</span>/<span
* class=SpellE>myUnhandleCatcher</span>"</span></i> <span
* style='color:teal'>/></span><o:p></o:p></span>
* </p>
*
* <p class=MsoNormal style='margin-bottom:0cm;margin-bottom:.0001pt;line-height: normal;mso-layout-grid-align:none;text-autospace:none'>
* <span style='font-size:
* 10.0pt;font-family:"Courier New";color:black;mso-ansi-language:DE'><span
* style='mso-tab-count:1'>����� </span></span><span style='font-size:10.0pt;
* font-family:"Courier New";color:teal;mso-ansi-language :DE'></</span><span
* class=SpellE><span style='font-size:10.0pt;font-family:"Courier New";
* color:#3F7F7F;mso-ansi-language:DE'>intent</span></span><span
* style='font-size: 10.0pt;font-family:"Courier New";color:#3F7F7F;mso-ansi-
* language:DE'>-filter</span><span style='font-size:10.0pt;font-family:"Courier
* New";color:teal;mso-ansi-language: DE'>></span><span
* style='font-size:10.0pt;font-family:"Courier New";
* mso-ansi-language:DE'><o:p></o:p></span>
* </p>
*
* <p class=MsoNormal>
* <span style='font-size:10.0pt;line-height:115%;font-family:
* "Courier New";color:teal;mso-ansi-language:DE'></</span><span
* class=SpellE><span
* style='font-size:10.0pt;line-height:115%;font-family:"Courier
* New";color:#3F7F7F; mso-ansi-language:DE'>activity</span></span><span
* style='font-size:10.0pt; line-height:115%;font-family:"Courier New";color:
* teal;mso-ansi-language:DE'>></span>
* </p>
*
* @author Spobo
*
*/
public class ErrorHandler extends Activity implements UncaughtExceptionHandler {
/**
* must be the same "x/y" string as in the AndroidManifest. </br></br>
*
*
* see {@link ErrorHandler} to understand where this is defined in the
* manifest
*/
public static String DATA_ANDROID_MIME_TYPE = "errors/myUnhandleCatcher";
private static final String LOG_TAG = "ErrorHandler";
private static final String PASSED_ERROR_TEXT_ID = "Error Text";
private static final String DEV_MAIL_ID = "dev mail";
private static final String MAIL_TITLE_ID = "title mail";
private static final String PASSED_FILE_ID = "file path";
private static Activity myCurrentActivity;
private static UncaughtExceptionHandler defaultHandler;
private static String myDeveloperMailAdress;
private static String myMailSubject = "Error in DroidAR";
/**
* DO NOT DELETE THIS CONSTURUCTOR!
*
* use the {@link ErrorHandler#ErrorHandler(Activity) constructor instead}.
* This constructor is required by the Android system and the
* {@link ErrorHandler} can only work properly if a activity is provided, so
* only use this constructor if you call
* {@link ErrorHandler#setCurrentActivity(Activity)} later!
*/
@Deprecated
// DO NOT DELETE THIS CONSTURUCTOR!
public ErrorHandler() {
if (defaultHandler == null) {
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
}
/**
* See {@link ErrorHandler} for details
*
* @param a
*/
public ErrorHandler(Activity a) {
setCurrentActivity(a);
if (defaultHandler == null) {
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
}
public static void showErrorActivity(Activity a, Exception errorToShow,
boolean keepBrokenProcessRunning) {
showErrorActivity(a, throwableToString(errorToShow), null,
keepBrokenProcessRunning);
}
/**
* @param a
* @param errorToShow
* @param optionalFilePathToSend
* something like "file://"+"/sdcard/folderA/fileB.jpg"
* @param keepBrokenProcessRunning
*/
public static void showErrorActivity(Activity a, Exception errorToShow,
String[] optionalFilePathToSend, boolean keepBrokenProcessRunning) {
showErrorActivity(a, throwableToString(errorToShow),
optionalFilePathToSend, keepBrokenProcessRunning);
}
public static String throwableToString(Throwable t) {
if (t == null) {
return "";
}
StringWriter sw = new StringWriter();
PrintWriter p = new PrintWriter(sw);
t.printStackTrace(p);
String s = sw.toString();
p.close();
if (t.getCause() != null) {
s += "\n\n Details about the cause for " + t + " was:\n"
+ throwableToString(t.getCause());
}
return s;
}
public static void showErrorActivity(final Activity activity,
final String errorText, String[] optionalFilePathsToSend,
boolean keepBrokenProcessRunning) {
if (activity != null) {
myCurrentActivity = activity;
Intent i = new Intent(Intent.ACTION_VIEW);
i.putExtra(PASSED_ERROR_TEXT_ID, errorText);
i.putExtra(PASSED_FILE_ID, optionalFilePathsToSend);
i.putExtra(DEV_MAIL_ID, myDeveloperMailAdress);
i.putExtra(MAIL_TITLE_ID, myMailSubject);
i.setType(DATA_ANDROID_MIME_TYPE);
Log.e("ErrorHandler", "Starting from " + activity + " to "
+ ErrorHandler.class);
activity.startActivity(i);
if (!keepBrokenProcessRunning) {
/*
* After displaying the error in a new process the current
* process can be killed. This wont affect the
* ErrorHandler-activity because it is running in its own
* process (see AndroidManifest)
*/
activity.finish();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String myErrorText = getIntent().getExtras().getString(
PASSED_ERROR_TEXT_ID);
String[] errorFiles = getIntent().getExtras().getStringArray(
PASSED_FILE_ID);
/*
* because this is a new process even the static fields will be reseted!
* the correct values can be restored by passing them in the intent
*/
myDeveloperMailAdress = getIntent().getExtras().getString(DEV_MAIL_ID);
myMailSubject = getIntent().getExtras().getString(MAIL_TITLE_ID);
setErrorContentView(this, myErrorText, errorFiles);
}
private static void setErrorContentView(final Activity a,
String myErrorText, String[] errorFilePaths) {
View v = loadModifier(a, myErrorText, myDeveloperMailAdress,
addDebugInfosToErrorMessage(a), errorFilePaths);
a.setContentView(v);
}
public static String mailText;
private static String[] savedErrorFilePaths;
private static boolean includeFiles = true;
public static View loadModifier(final Activity a,
final String exceptionText, String myDeveloperMailAdress,
final String deviceDebugInformation, final String[] errorFilePaths) {
savedErrorFilePaths = errorFilePaths;
final M_Container c = new M_Container();
if (myMailSubject != null && !myMailSubject.equals("")) {
c.add(new M_Caption(myMailSubject));
} else {
c.add(new M_Caption("The application crashed"));
}
c.add(new M_InfoText(R.drawable.ic_dialog_alert,
"We are sorry the application had a problem "
+ "with your device. \n\n You can send "
+ "the error to us so that we can fix this bug."));
M_TextInput problemDescr = new M_TextInput(true, true, false) {
@Override
public boolean save(String newText) {
mailText += "[" + getVarName() + "]\n" + newText;
if (exceptionText != null) {
mailText += "\n\n[Error Information]\n" + exceptionText;
}
return true;
}
@Override
public String load() {
return "";
}
@Override
public String getVarName() {
return "Your problem description";
}
};
if (exceptionText != null && !exceptionText.equals("")) {
problemDescr
.setInfoText("You can add some information about the problem here..");
} else {
problemDescr.setInfoText("Write your problem down here...");
}
c.add(problemDescr);
if (errorFilePaths != null) {
c.add(new M_Checkbox() {
@Override
public boolean save(boolean newValue) {
includeFiles = newValue;
return true;
}
@Override
public boolean loadVar() {
return true;
}
@Override
public CharSequence getVarName() {
return "Include files to allow reconstructing the error";
}
});
}
if (myDeveloperMailAdress != null) {
c.add(new M_Button("Send error mail to developers..") {
@Override
public void onClick(Context context, Button clickedButton) {
mailText = ""; // clear mail text
c.save();
sendMail(a, mailText);
a.finish();
}
});
}
if (exceptionText != null && !exceptionText.equals("")) {
c.add(new M_TextInput(false, true, false) {
@Override
public boolean save(String newText) {
// will never be called because editable flag is false
return true;
}
@Override
public String load() {
return exceptionText;
}
@Override
public String getVarName() {
return "Error Information";
}
});
}
if (deviceDebugInformation != null) {
c.add(new M_TextInput(true, true, true) {
@Override
public boolean save(String newText) {
mailText += "\n\n\n[" + getVarName() + "]\n" + newText;
return true;
}
@Override
public String load() {
return deviceDebugInformation;
}
@Override
public String getVarName() {
return "Device Information";
}
});
}
return c.getView(a);
}
public static void sendMail(Context context, String emailText) {
// need to "send multiple" to get more than one attachment
Intent emailIntent = new Intent(
android.content.Intent.ACTION_SEND_MULTIPLE);
if (!includeFiles || savedErrorFilePaths == null
|| savedErrorFilePaths.length == 1) {
// if no files appended use the default intent type
emailIntent = new Intent(android.content.Intent.ACTION_SEND);
}
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
myMailSubject);
emailIntent.setType("*/*");
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
new String[] { myDeveloperMailAdress });
emailIntent.putExtra(Intent.EXTRA_TEXT, emailText);
if (includeFiles && savedErrorFilePaths != null) {
// has to be an ArrayList
ArrayList<Uri> uris = new ArrayList<Uri>();
// convert from paths to Android friendly Parcelable Uri's
for (int i = 0; i < savedErrorFilePaths.length; i++) {
File fileIn = new File(savedErrorFilePaths[i]);
Uri u = Uri.fromFile(fileIn);
if (fileIn.exists()) {
uris.add(u);
}
}
emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
}
context.startActivity(Intent.createChooser(emailIntent, "Send mail..."));
}
private static String addDebugInfosToErrorMessage(Activity a) {
String s = "";
try {
PackageInfo pInfo = a.getPackageManager().getPackageInfo(
a.getPackageName(), PackageManager.GET_META_DATA);
s += "\n APP Package Name: " + a.getPackageName();
s += "\n APP Version Name: " + pInfo.versionName;
s += "\n APP Version Code: " + pInfo.versionCode;
s += "\n";
} catch (NameNotFoundException e) {
}
s += "\n OS Version: " + System.getProperty("os.version") + " ("
+ android.os.Build.VERSION.INCREMENTAL + ")";
s += "\n OS API Level: " + android.os.Build.VERSION.SDK;
s += "\n Device: " + android.os.Build.DEVICE;
s += "\n Model (and Product): " + android.os.Build.MODEL + " ("
+ android.os.Build.PRODUCT + ")";
// TODO add application version!
// more from
// http://developer.android.com/reference/android/os/Build.html :
s += "\n Manufacturer: " + android.os.Build.MANUFACTURER;
s += "\n Other TAGS: " + android.os.Build.TAGS;
s += "\n screenWidth: "
+ a.getWindow().getWindowManager().getDefaultDisplay()
.getWidth();
s += "\n screenHeigth: "
+ a.getWindow().getWindowManager().getDefaultDisplay()
.getHeight();
s += "\n Keyboard available: "
+ (a.getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS);
s += "\n Trackball available: "
+ (a.getResources().getConfiguration().navigation == Configuration.NAVIGATION_TRACKBALL);
s += "\n SD Card state: " + Environment.getExternalStorageState();
Properties p = System.getProperties();
Enumeration keys = p.keys();
String key = "";
while (keys.hasMoreElements()) {
key = (String) keys.nextElement();
s += "\n > " + key + " = " + (String) p.get(key);
}
return s;
}
@Override
public void uncaughtException(final Thread thread, final Throwable ex) {
Log.e(LOG_TAG, "A wild 'Uncaught exeption' appeares!");
ex.printStackTrace();
if (myCurrentActivity != null) {
Log.e("ErrorHandler", "Starting error activity");
showErrorActivity(myCurrentActivity, throwableToString(ex), null,
false);
} else {
Log.e("ErrorHandler",
"No current activity set -> error activity couldn't be started");
defaultHandler.uncaughtException(thread, ex);
}
}
public static void enableEmailReports(String developerEmailAdress,
String emailTitle) {
myDeveloperMailAdress = developerEmailAdress;
myMailSubject = emailTitle;
}
public static void setCurrentActivity(Activity a) {
myCurrentActivity = a;
}
/**
* @param a
* @param data_android_mimeType
* something like 'errors/myUnhandleCatcher' <br>
* set this value also in the manifest where the error handler
* activity is registered
*/
public static void registerNewErrorHandler(Activity a,
String data_android_mimeType) {
registerNewErrorHandler(a);
DATA_ANDROID_MIME_TYPE = data_android_mimeType;
}
/**
* use {@link ErrorHandler#registerNewErrorHandler(Activity, String)}
* instead
*
* @param currentActivity
*/
@Deprecated
public static void registerNewErrorHandler(Activity currentActivity) {
Thread.setDefaultUncaughtExceptionHandler(new ErrorHandler(
currentActivity));
}
}