diff --git a/shell/android/res/values/strings.xml b/shell/android/res/values/strings.xml
index d2d027818..9c0396c89 100644
--- a/shell/android/res/values/strings.xml
+++ b/shell/android/res/values/strings.xml
@@ -113,4 +113,7 @@
be created in VmuBackups folder!
Upload VMU
Download VMU
+
+ Copying logcat content to clipboard\nPlease paste in the issue report
+ Log saved to \"Files Dir\" path
\ No newline at end of file
diff --git a/shell/android/src/com/reicast/emulator/MainActivity.java b/shell/android/src/com/reicast/emulator/MainActivity.java
index 75c6d4428..68d9b7964 100644
--- a/shell/android/src/com/reicast/emulator/MainActivity.java
+++ b/shell/android/src/com/reicast/emulator/MainActivity.java
@@ -13,6 +13,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
@@ -22,10 +23,10 @@ import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.view.View.OnSystemUiVisibilityChangeListener;
import android.view.View.OnTouchListener;
+import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
@@ -35,6 +36,7 @@ import com.jeremyfeinstein.slidingmenu.lib.app.SlidingFragmentActivity;
import com.reicast.emulator.config.Config;
import com.reicast.emulator.config.InputFragment;
import com.reicast.emulator.config.OptionsFragment;
+import com.reicast.emulator.debug.GenerateLogs;
import com.reicast.emulator.emu.JNIdc;
import com.reicast.emulator.periph.Gamepad;
@@ -322,7 +324,11 @@ public class MainActivity extends SlidingFragmentActivity implements
}
});
} else {
- messages.setVisibility(View.GONE);
+ messages.setOnClickListener(new OnClickListener() {
+ public void onClick(View view) {
+ generateErrorLog();
+ }
+ });
}
}
});
@@ -336,6 +342,17 @@ public class MainActivity extends SlidingFragmentActivity implements
}
});
}
+
+ public void generateErrorLog() {
+ GenerateLogs mGenerateLogs = new GenerateLogs(MainActivity.this);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ mGenerateLogs.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
+ getFilesDir().getAbsolutePath());
+ } else {
+ mGenerateLogs.execute(getFilesDir().getAbsolutePath());
+ }
+
+ }
/**
* Display a dialog to notify the user of prior crash
diff --git a/shell/android/src/com/reicast/emulator/config/Config.java b/shell/android/src/com/reicast/emulator/config/Config.java
index 44a35db83..c6e670fad 100644
--- a/shell/android/src/com/reicast/emulator/config/Config.java
+++ b/shell/android/src/com/reicast/emulator/config/Config.java
@@ -58,6 +58,10 @@ public class Config {
public static String cheatdisk = "null";
public static boolean nativeact = false;
public static int vibrationDuration = 20;
+
+ public static String git_issues = "https://github.com/reicast/reicast-emulator/issues/";
+ public static String log_url = "http://loungekatt.no-ip.biz:3194/ReicastBot/report/submit.php";
+ public static String report_url = "http://loungekatt.no-ip.biz:3194/ReicastBot/report/logs/";
private SharedPreferences mPrefs;
diff --git a/shell/android/src/com/reicast/emulator/debug/GenerateLogs.java b/shell/android/src/com/reicast/emulator/debug/GenerateLogs.java
new file mode 100644
index 000000000..b22db1606
--- /dev/null
+++ b/shell/android/src/com/reicast/emulator/debug/GenerateLogs.java
@@ -0,0 +1,246 @@
+package com.reicast.emulator.debug;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import com.reicast.emulator.R;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.widget.Toast;
+
+public class GenerateLogs extends AsyncTask {
+
+ public static final String build_model = android.os.Build.MODEL;
+ public static final String build_device = android.os.Build.DEVICE;
+ public static final String build_board = android.os.Build.BOARD;
+ public static final int build_sdk = android.os.Build.VERSION.SDK_INT;
+
+ public static final String DN = "Donut";
+ public static final String EC = "Eclair";
+ public static final String FR = "Froyo";
+ public static final String GB = "Gingerbread";
+ public static final String HC = "Honeycomb";
+ public static final String IC = "Ice Cream Sandwich";
+ public static final String JB = "JellyBean";
+ public static final String KK = "KitKat";
+ public static final String NF = "Not Found";
+
+ private String unHandledIOE;
+
+ private Context mContext;
+ private String currentTime;
+
+ public GenerateLogs(Context mContext) {
+ this.mContext = mContext;
+ this.currentTime = String.valueOf(System.currentTimeMillis());
+ }
+
+ /**
+ * Obtain the specific parameters of the current device
+ *
+ */
+ private String discoverCPUData() {
+ String s = "MODEL: " + Build.MODEL;
+ s += "\r\n";
+ s += "DEVICE: " + build_device;
+ s += "\r\n";
+ s += "BOARD: " + build_board;
+ s += "\r\n";
+ if (String.valueOf(build_sdk) != null) {
+ String build_version = NF;
+ if (build_sdk >= 4 && build_sdk < 7) {
+ build_version = DN;
+ } else if (build_sdk == 7) {
+ build_version = EC;
+ } else if (build_sdk == 8) {
+ build_version = FR;
+ } else if (build_sdk >= 9 && build_sdk < 11) {
+ build_version = GB;
+ } else if (build_sdk >= 11 && build_sdk < 14) {
+ build_version = HC;
+ } else if (build_sdk >= 14 && build_sdk < 16) {
+ build_version = IC;
+ } else if (build_sdk >= 16 && build_sdk < 19) {
+ build_version = JB;
+ } else if (build_sdk >= 19) {
+ build_version = KK;
+ }
+ s += build_version + " (" + build_sdk + ")";
+ } else {
+ String prop_build_version = "ro.build.version.release";
+ String prop_sdk_version = "ro.build.version.sdk";
+ String build_version = readOutput("/system/bin/getprop "
+ + prop_build_version);
+ String sdk_version = readOutput("/system/bin/getprop "
+ + prop_sdk_version);
+ s += build_version + " (" + sdk_version + ")";
+ }
+ return s;
+ }
+
+ /**
+ * Read the output of a shell command
+ *
+ * @param command
+ * The shell command being issued to the terminal
+ */
+ public static String readOutput(String command) {
+ try {
+ Process p = Runtime.getRuntime().exec(command);
+ InputStream is = null;
+ if (p.waitFor() == 0) {
+ is = p.getInputStream();
+ } else {
+ is = p.getErrorStream();
+ }
+ BufferedReader br = new BufferedReader(new InputStreamReader(is),
+ 2048);
+ String line = br.readLine();
+ br.close();
+ return line;
+ } catch (Exception ex) {
+ return "ERROR: " + ex.getMessage();
+ }
+ }
+
+ public void setUnhandled(String unHandledIOE) {
+ this.unHandledIOE = unHandledIOE;
+ }
+
+ @Override
+ protected String doInBackground(String... params) {
+ File logFile = new File(params[0], currentTime + ".txt");
+ Process mLogcatProc = null;
+ BufferedReader reader = null;
+ final StringBuilder log = new StringBuilder();
+ String separator = System.getProperty("line.separator");
+ log.append(discoverCPUData());
+ if (unHandledIOE != null) {
+ log.append(separator);
+ log.append(separator);
+ log.append("Unhandled Exceptions");
+ log.append(separator);
+ log.append(separator);
+ log.append(unHandledIOE);
+ }
+ try {
+ mLogcatProc = Runtime.getRuntime().exec(
+ new String[] { "logcat", "-d", "AndroidRuntime:E *:S" });
+ reader = new BufferedReader(new InputStreamReader(
+ mLogcatProc.getInputStream()));
+ String line;
+ log.append(separator);
+ log.append(separator);
+ log.append("AndroidRuntime Output");
+ log.append(separator);
+ log.append(separator);
+ while ((line = reader.readLine()) != null) {
+ log.append(line);
+ log.append(separator);
+ }
+ reader.close();
+ mLogcatProc = null;
+ reader = null;
+ int PID = android.os.Process.getUidForName("com.reicast.emulator");
+ mLogcatProc = Runtime.getRuntime().exec(
+ new String[] { "logcat", "-d", "|", "grep " + PID });
+ reader = new BufferedReader(new InputStreamReader(
+ mLogcatProc.getInputStream()));
+ log.append(separator);
+ log.append(separator);
+ log.append("Application Core Output");
+ log.append(separator);
+ log.append(separator);
+ while ((line = reader.readLine()) != null) {
+ log.append(line);
+ log.append(separator);
+ }
+ reader.close();
+ mLogcatProc = null;
+ reader = null;
+ mLogcatProc = Runtime.getRuntime().exec(
+ new String[] { "logcat", "-d", "reidc:V *:S" });
+ reader = new BufferedReader(new InputStreamReader(
+ mLogcatProc.getInputStream()));
+ log.append(separator);
+ log.append(separator);
+ log.append("Native Interface Output");
+ log.append(separator);
+ log.append(separator);
+ while ((line = reader.readLine()) != null) {
+ log.append(line);
+ log.append(separator);
+ }
+ reader.close();
+ mLogcatProc = null;
+ reader = null;
+ mLogcatProc = Runtime.getRuntime().exec(
+ new String[] { "logcat", "-d", "GL2JNIView:E *:S" });
+ reader = new BufferedReader(new InputStreamReader(
+ mLogcatProc.getInputStream()));
+ log.append(separator);
+ log.append(separator);
+ log.append("Open GLES View Output");
+ log.append(separator);
+ log.append(separator);
+ while ((line = reader.readLine()) != null) {
+ log.append(line);
+ log.append(separator);
+ }
+ reader.close();
+ mLogcatProc = null;
+ reader = null;
+ File memory = new File(mContext.getFilesDir(), "mem_alloc.txt");
+ if (memory.exists()) {
+ log.append(separator);
+ log.append(separator);
+ log.append("Memory Allocation Table");
+ log.append(separator);
+ log.append(separator);
+ FileInputStream fis = new FileInputStream(memory);
+ reader = new BufferedReader(new InputStreamReader(fis));
+ while ((line = reader.readLine()) != null) {
+ log.append(line);
+ log.append(separator);
+ }
+ fis.close();
+ fis = null;
+ reader.close();
+ reader = null;
+ }
+ BufferedWriter writer = new BufferedWriter(new FileWriter(logFile));
+ writer.write(log.toString());
+ writer.flush();
+ writer.close();
+ return log.toString();
+ } catch (IOException e) {
+
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(final String response) {
+ if (response != null && !response.equals(null)) {
+ Toast.makeText(mContext, mContext.getString(R.string.log_saved),
+ Toast.LENGTH_SHORT).show();
+ Toast.makeText(mContext, mContext.getString(R.string.platform),
+ Toast.LENGTH_SHORT).show();
+ UploadLogs mUploadLogs = new UploadLogs(mContext, currentTime);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ mUploadLogs.executeOnExecutor(
+ AsyncTask.THREAD_POOL_EXECUTOR, response);
+ } else {
+ mUploadLogs.execute(response);
+ }
+ }
+ }
+}
diff --git a/shell/android/src/com/reicast/emulator/debug/UploadLogs.java b/shell/android/src/com/reicast/emulator/debug/UploadLogs.java
new file mode 100644
index 000000000..a776e3a99
--- /dev/null
+++ b/shell/android/src/com/reicast/emulator/debug/UploadLogs.java
@@ -0,0 +1,140 @@
+package com.reicast.emulator.debug;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import com.reicast.emulator.config.Config;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.StrictMode;
+
+/**
+ * Upload the specialized logcat to reicast issues
+ *
+ * @param mContext
+ * The context this method will be executed from
+ * @param currentTime
+ * The system time at which the log was made
+ */
+public class UploadLogs extends AsyncTask {
+
+ private String currentTime;
+ private String logUrl;
+ private Context mContext;
+
+ public UploadLogs(Context mContext, String currentTime) {
+ this.mContext = mContext;
+ this.currentTime = currentTime;
+ }
+
+ private void RedirectSubmission(Header[] headers, String content) {
+ UploadLogs mUploadLogs = new UploadLogs(mContext,
+ currentTime);
+ mUploadLogs.setPostUrl(headers[headers.length - 1]
+ .getValue());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ mUploadLogs.executeOnExecutor(
+ AsyncTask.THREAD_POOL_EXECUTOR, content);
+ } else {
+ mUploadLogs.execute(content);
+ }
+ }
+
+ /**
+ * Set the URL for where the log will be uploaded
+ *
+ * @param logUrl
+ * The URL of the log upload server
+ */
+ public void setPostUrl(String logUrl) {
+ this.logUrl = logUrl;
+ }
+
+ @SuppressLint("NewApi")
+ protected void onPreExecute() {
+ if (logUrl == null || logUrl.equals(null)) {
+ logUrl = Config.log_url;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+ StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
+ .permitAll().build();
+ StrictMode.setThreadPolicy(policy);
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @Override
+ protected Object doInBackground(String... params) {
+ HttpClient client = new DefaultHttpClient();
+ HttpPost post = new HttpPost(logUrl);
+ try {
+ ArrayList mPairs = new ArrayList();
+ mPairs.add(new BasicNameValuePair("name", currentTime));
+ mPairs.add(new BasicNameValuePair("issue", params[0]));
+ post.setEntity(new UrlEncodedFormEntity(mPairs));
+ HttpResponse getResponse = client.execute(post);
+ final int statusCode = getResponse.getStatusLine().getStatusCode();
+ if (statusCode != HttpStatus.SC_OK) {
+ Header[] headers = getResponse.getHeaders("Location");
+ if (headers != null && headers.length != 0) {
+ RedirectSubmission(headers, params[0]);
+ } else {
+ return null;
+ }
+ } else {
+ return EntityUtils.toString(getResponse.getEntity());
+ }
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ post.abort();
+ } catch (IOException e) {
+ e.printStackTrace();
+ post.abort();
+ } catch (Exception e) {
+ e.printStackTrace();
+ post.abort();
+ }
+ return null;
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @SuppressWarnings("deprecation")
+ @Override
+ protected void onPostExecute(Object response) {
+ if (response != null && !response.equals(null)) {
+ String logLink = Config.report_url + currentTime + ".txt";
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ android.content.ClipboardManager clipboard = (android.content.ClipboardManager) mContext
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ android.content.ClipData clip = android.content.ClipData
+ .newPlainText("logcat", logLink);
+ clipboard.setPrimaryClip(clip);
+ } else {
+ android.text.ClipboardManager clipboard = (android.text.ClipboardManager) mContext
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboard.setText(logLink);
+ }
+ Intent browserIntent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse(Config.git_issues));
+ mContext.startActivity(browserIntent);
+ }
+ }
+}