/* * Copyright (C) 2010 Mark Wyszomierski * * Portions Copyright (C) 2009 Xtralogic, Inc. */ package org.ohmage.activity; import org.ohmage.AccountHelper; import org.ohmage.R; import org.ohmage.UserPreferencesHelper; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.util.Log; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; /** * This is taken from the android-log-collector project here: * * http://code.google.com/p/android-log-collector/ * * so as we can dump the last set of system logs from the user's device at the * bottom of their feedback email. If they are reporting a crash, the logs * might show exceptions etc. Android 2.2+ reports this directly to the marketplace * for us so this will be phased out eventually. * * @date July 8, 2010 * @author Mark Wyszomierski (markww@gmail.com) * */ public class SendLogActivity extends Activity { public final static String TAG = "SendLogActivity"; private static final String FEEDBACK_EMAIL_ADDRESS = "mobilize.tech@gmail.com"; private static final String LINE_SEPARATOR = System.getProperty("line.separator"); private AlertDialog mMainDialog; private Intent mSendIntent; private CollectLogTask mCollectLogTask; private ProgressDialog mProgressDialog; private String mAdditonalInfo; private String[] mFilterSpecs; private String mFormat; private String mBuffer; @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); mSendIntent = new Intent(Intent.ACTION_SEND); String timestamp = new SimpleDateFormat("MM/dd/yy H:mm:ss").format(new Date()); mSendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.crash_report_subject, timestamp)); mSendIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { FEEDBACK_EMAIL_ADDRESS }); mSendIntent.setType("message/rfc822"); mSendIntent.setPackage("com.google.android.gm"); StringBuilder body = new StringBuilder(); Resources res = getResources(); body.append(res.getString(R.string.crash_report_more)); body.append(LINE_SEPARATOR); body.append(LINE_SEPARATOR); mSendIntent.putExtra(Intent.EXTRA_TEXT, body.toString()); AccountHelper prefs = new AccountHelper(this); StringBuilder logHeader = new StringBuilder(); logHeader.append("--------------------------------------"); try { logHeader.append(LINE_SEPARATOR); logHeader.append("ver: "); logHeader.append(getPackageManager().getPackageInfo("org.ohmage", 0).versionName); } catch (NameNotFoundException e) { Log.e(TAG, "unable to retrieve current version code", e); } logHeader.append(LINE_SEPARATOR); logHeader.append("user: "); logHeader.append(prefs.getUsername()); logHeader.append(LINE_SEPARATOR); logHeader.append("p: "); logHeader.append(Build.MODEL); logHeader.append(LINE_SEPARATOR); logHeader.append("os: "); logHeader.append(Build.VERSION.RELEASE); logHeader.append(LINE_SEPARATOR); logHeader.append("build#: "); logHeader.append(Build.DISPLAY); logHeader.append(LINE_SEPARATOR); logHeader.append(LINE_SEPARATOR); logHeader.append("--------------------------------------"); logHeader.append(LINE_SEPARATOR); mAdditonalInfo = logHeader.toString(); collectAndSendLog(); } @SuppressWarnings("unchecked") void collectAndSendLog(){ /*Usage: logcat [options] [filterspecs] options include: -s Set default filter to silent. Like specifying filterspec '*:s' -f <filename> Log to file. Default to stdout -r [<kbytes>] Rotate log every kbytes. (16 if unspecified). Requires -f -n <count> Sets max number of rotated logs to <count>, default 4 -v <format> Sets the log print format, where <format> is one of: brief process tag thread raw time threadtime long -c clear (flush) the entire log and exit -d dump the log and then exit (don't block) -g get the size of the log's ring buffer and exit -b <buffer> request alternate ring buffer ('main' (default), 'radio', 'events') -B output the log in binary filterspecs are a series of <tag>[:priority] where <tag> is a log component tag (or * for all) and priority is: V Verbose D Debug I Info W Warn E Error F Fatal S Silent (supress all output) '*' means '*:d' and <tag> by itself means <tag>:v If not specified on the commandline, filterspec is set from ANDROID_LOG_TAGS. If no filterspec is found, filter defaults to '*:I' If not specified with -v, format is set from ANDROID_PRINTF_LOG or defaults to "brief"*/ ArrayList<String> list = new ArrayList<String>(); if (mFormat != null){ list.add("-v"); list.add(mFormat); } if (mBuffer != null){ list.add("-b"); list.add(mBuffer); } if (mFilterSpecs != null){ for (String filterSpec : mFilterSpecs){ list.add(filterSpec); } } mCollectLogTask = (CollectLogTask) new CollectLogTask().execute(list); } private class CollectLogTask extends AsyncTask<ArrayList<String>, Void, File>{ @Override protected void onPreExecute(){ showProgressDialog(getString(R.string.crash_report_acquiring_logs)); } @Override protected File doInBackground(ArrayList<String>... params){ File file = null; try{ ArrayList<String> commandLine = new ArrayList<String>(); commandLine.add("logcat");//$NON-NLS-1$ commandLine.add("-d");//$NON-NLS-1$ ArrayList<String> arguments = ((params != null) && (params.length > 0)) ? params[0] : null; if (null != arguments){ commandLine.addAll(arguments); } Process process = Runtime.getRuntime().exec(commandLine.toArray(new String[0])); file = File.createTempFile("ohmage_crash", ".log", Environment.getExternalStorageDirectory()); OutputStream out=new FileOutputStream(file); out.write(mAdditonalInfo.getBytes()); InputStream is = process.getInputStream(); byte buf[]=new byte[1024]; int len; while((len=is.read(buf))>0) out.write(buf,0,len); out.close(); is.close(); } catch (IOException e){ Log.e(TAG, "CollectLogTask.doInBackground failed", e);//$NON-NLS-1$ } return file; } @Override protected void onPostExecute(File file){ if (file != null && file.exists()) { mSendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://"+ file.getAbsolutePath())); startActivity(Intent.createChooser(mSendIntent, getString(R.string.crash_report_chooser_title))); dismissProgressDialog(); dismissMainDialog(); finish(); } else{ dismissProgressDialog(); showErrorDialog(getString(R.string.crash_report_error)); } } } void showErrorDialog(String errorMessage){ new AlertDialog.Builder(this) .setTitle(getString(R.string.app_name)) .setMessage(errorMessage) .setIcon(android.R.drawable.ic_dialog_alert) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){ @Override public void onClick(DialogInterface dialog, int whichButton){ finish(); } }) .show(); } void dismissMainDialog(){ if (null != mMainDialog && mMainDialog.isShowing()){ mMainDialog.dismiss(); mMainDialog = null; } } void showProgressDialog(String message){ mProgressDialog = new ProgressDialog(this); mProgressDialog.setIndeterminate(true); mProgressDialog.setMessage(message); mProgressDialog.setCancelable(true); mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener(){ @Override public void onCancel(DialogInterface dialog){ cancellCollectTask(); finish(); } }); mProgressDialog.show(); } private void dismissProgressDialog(){ if (null != mProgressDialog && mProgressDialog.isShowing()) { mProgressDialog.dismiss(); mProgressDialog = null; } } void cancellCollectTask(){ if (mCollectLogTask != null && mCollectLogTask.getStatus() == AsyncTask.Status.RUNNING) { mCollectLogTask.cancel(true); mCollectLogTask = null; } } @Override protected void onPause(){ cancellCollectTask(); dismissProgressDialog(); dismissMainDialog(); super.onPause(); } }