/* * Copyright 2012 The Stanford MobiSocial Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * 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 mobisocial.metrics; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; import java.util.ArrayList; import java.util.Date; import java.util.List; import mobisocial.musubi.App; import mobisocial.musubi.BootstrapActivity; import mobisocial.musubi.model.MIdentity; import mobisocial.musubi.model.helpers.IdentitiesManager; import mobisocial.musubi.ui.MusubiBaseActivity; import mobisocial.musubi.ui.SettingsActivity; import mobisocial.musubi.ui.util.UiUtil; import mobisocial.musubi.util.CertifiedHttpClient; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.protocol.HTTP; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import android.util.Log; public class MusubiExceptionHandler implements UncaughtExceptionHandler { private final String TAG = getClass().getSimpleName(); private final Context mContext; private final UncaughtExceptionHandler mDefaultHandler; public MusubiExceptionHandler(Context context, UncaughtExceptionHandler defaultHandler) { mContext = context; mDefaultHandler = defaultHandler; } @Override public void uncaughtException(final Thread thread, final Throwable ex) { new Thread() { @Override public void run() { try { // force upgrade check on next boot. SharedPreferences p = mContext.getSharedPreferences( SettingsActivity.PREFS_NAME, 0); p.edit().putLong(BootstrapActivity.PREF_LAST_UPDATE_CHECK, 0).commit(); // submit to chirp. submitException(ex); } catch (IOException e) { // TODO: put in UserMetrics database for later submission Log.e(TAG, "failed to post message", e); } } }.start(); Log.e(TAG, "Uncaught exception", ex); if (mDefaultHandler != null) { mDefaultHandler.uncaughtException(thread, ex); } } private void submitException(Throwable ex) throws IOException { if (UsageMetrics.CHIRP_REPORTING_ENDPOINT == null) { return; } try { HttpClient http = getHttpClient(mContext); HttpPost post = new HttpPost(UsageMetrics.CHIRP_REPORTING_ENDPOINT); List<NameValuePair> data = new ArrayList<NameValuePair>(); JSONObject json = jsonForException(mContext, ex, false); data.add(new BasicNameValuePair("json", json.toString())); post.setEntity(new UrlEncodedFormEntity(data, HTTP.UTF_8)); HttpResponse response = http.execute(post); response.getEntity(); int status = response.getStatusLine().getStatusCode(); if (status != HttpStatus.SC_OK) { throw new IOException("Failed to post message to server. Http code: " + status); } } catch (ClientProtocolException e) { throw new IOException("Protocol exception while posting to server", e); } } static JSONObject jsonForException(Context context, Throwable ex, boolean caught) { JSONObject json = new JSONObject(); try { Writer traceWriter = new StringWriter(); PrintWriter printer = new PrintWriter(traceWriter); ex.printStackTrace(printer); json.put("type", "exception"); json.put("caught", caught); json.put("app", context.getPackageName()); json.put("message", ex.getMessage()); json.put("trace", traceWriter.toString()); json.put("timestamp", Long.toString(new Date().getTime())); boolean devmode = MusubiBaseActivity.isDeveloperModeEnabled(context); json.put("musubi_devmode", Boolean.toString(devmode)); if (devmode) { IdentitiesManager im = new IdentitiesManager(App.getDatabaseSource(context)); MIdentity id = im.getMyDefaultIdentity(); String user = "Unknown"; if (id != null) { user = UiUtil.safeNameForIdentity(id); } json.put("musubi_devmode_user_id", user); user = App.getMusubi(context).userForLocalDevice(null).getName(); json.put("musubi_devmode_user_name", user); } try { PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); json.put("musubi_version_name", info.versionName); json.put("musubi_version_code", Integer.toString(info.versionCode)); } catch (NameNotFoundException e) { // shouldn't happen, but not fatal. } json.put("android_api", Integer.toString(Build.VERSION.SDK_INT)); json.put("android_release", Build.VERSION.RELEASE); json.put("android_model", Build.MODEL); json.put("android_make", Build.MANUFACTURER); } catch (JSONException e) {} return json; } static final HttpClient getHttpClient(Context context) { HttpClient http = new CertifiedHttpClient(context); HttpParams params = http.getParams(); HttpProtocolParams.setUseExpectContinue(params, false); HttpConnectionParams.setConnectionTimeout(params, 6000); HttpConnectionParams.setSoTimeout(params, 6000); return http; } public static void installHandler(Context context) { UncaughtExceptionHandler currentHandler = Thread.getDefaultUncaughtExceptionHandler(); if (!(currentHandler instanceof MusubiExceptionHandler)) { Thread.setDefaultUncaughtExceptionHandler( new MusubiExceptionHandler(context, currentHandler)); } } }