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); + } + } +}