PageRenderTime 24ms CodeModel.GetById 12ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/TalkBack/src/com/google/android/marvin/talkback/TalkBackExceptionHandler.java

http://eyes-free.googlecode.com/
Java | 207 lines | 144 code | 41 blank | 22 comment | 14 complexity | b8ccfd457bba597a98c5e33815e11d68 MD5 | raw file
  1// Copyright 2011 Google Inc. All Rights Reserved.
  2
  3package com.google.android.marvin.talkback;
  4
  5import android.app.Notification;
  6import android.app.NotificationManager;
  7import android.app.PendingIntent;
  8import android.content.Context;
  9import android.content.Intent;
 10import android.content.pm.PackageManager;
 11import android.os.Build;
 12import android.view.accessibility.AccessibilityEvent;
 13
 14import java.io.BufferedReader;
 15import java.io.File;
 16import java.io.FileNotFoundException;
 17import java.io.FileOutputStream;
 18import java.io.FileReader;
 19import java.io.IOException;
 20import java.lang.Thread.UncaughtExceptionHandler;
 21import java.lang.ref.WeakReference;
 22import java.util.Date;
 23
 24/**
 25 * @author alanv@google.com (Alan Viverette)
 26 */
 27public class TalkBackExceptionHandler implements UncaughtExceptionHandler {
 28    /** The filename used for logging crash reports. */
 29    private static final String TALKBACK_CRASH_LOG = "talkback_crash.log";
 30
 31    /** Notification identifier for the crash report notification. */
 32    private static final int CRASH_NOTIFICATION_ID = 1;
 33
 34    /** The email addresses that receive TalkBack crash reports. */
 35    private static final String[] CRASH_REPORT_EMAILS = {
 36        "eyes.free.crash.reports@gmail.com"
 37    };
 38
 39    private final WeakReference<Context> mContext;
 40    private final UncaughtExceptionHandler mDefault;
 41
 42    private String mLastSpokenEvent;
 43
 44    public TalkBackExceptionHandler(Context context) {
 45        mContext = new WeakReference<Context>(context);
 46        mDefault = Thread.getDefaultUncaughtExceptionHandler();
 47    }
 48
 49    public void register() {
 50        Thread.setDefaultUncaughtExceptionHandler(this);
 51    }
 52
 53    public void unregister() {
 54        Thread.setDefaultUncaughtExceptionHandler(null);
 55    }
 56
 57    public void setLastSpokenEvent(AccessibilityEvent event) {
 58        mLastSpokenEvent = event.toString();
 59    }
 60
 61    @Override
 62    public void uncaughtException(Thread thread, Throwable ex) {
 63        final Context context = mContext.get();
 64
 65        if (context == null) {
 66            unregister();
 67            mDefault.uncaughtException(thread, ex);
 68            return;
 69        }
 70
 71        final PackageManager packageManager = context.getPackageManager();
 72
 73        int version;
 74
 75        try {
 76            version = packageManager.getPackageInfo(context.getPackageName(), 0).versionCode;
 77        } catch (Exception e) {
 78            version = 0;
 79        }
 80
 81        final StringBuilder stackTrace = new StringBuilder();
 82        final String timestamp = new Date().toString();
 83        final String androidOsVersion =
 84                String.format("%d - %s", Build.VERSION.SDK_INT, Build.VERSION.INCREMENTAL);
 85        final String deviceInfo =
 86                String.format("%s, %s, %s", Build.MANUFACTURER, Build.MODEL, Build.PRODUCT);
 87        final String talkBackVersion = String.format("%d", version);
 88
 89        stackTrace.append(ex.toString());
 90        stackTrace.append('\n');
 91
 92        for (StackTraceElement element : ex.getStackTrace()) {
 93            stackTrace.append(element.toString());
 94            stackTrace.append('\n');
 95        }
 96
 97        final String report =
 98                context.getString(R.string.template_crash_report_message, timestamp,
 99                        androidOsVersion, deviceInfo, talkBackVersion, stackTrace, mLastSpokenEvent);
100
101        try {
102            final FileOutputStream fos =
103                    context.openFileOutput(TALKBACK_CRASH_LOG, Context.MODE_APPEND);
104            fos.write(report.getBytes());
105            fos.flush();
106            fos.close();
107        } catch (Exception e) {
108            e.printStackTrace();
109        }
110
111        if (mDefault != null) {
112            mDefault.uncaughtException(thread, ex);
113        }
114    }
115
116    /**
117     * Checks the TalkBack crash log and prompts the user to submit reports if
118     * appropriate.
119     */
120    public void processCrashReport() {
121        final CharSequence crashReport = loadReport();
122
123        if (crashReport != null) {
124            displayNotification(crashReport);
125        }
126    }
127
128    /**
129     * Attempts to load the most recently saved crash report. Returns
130     * {@code null} on failure.
131     * 
132     * @return The contents of the most recently saved crash report (or
133     *         {@code null} on failure.
134     */
135    private CharSequence loadReport() {
136        final Context context = mContext.get();
137
138        if (context == null) {
139            return null;
140        }
141
142        final String logPath = context.getFilesDir() + File.separator + TALKBACK_CRASH_LOG;
143        final File logFile = new File(logPath);
144
145        if (!logFile.exists()) {
146            return null;
147        }
148
149        final StringBuilder crashReport = new StringBuilder();
150
151        try {
152            final FileReader fileReader = new FileReader(logFile);
153            final BufferedReader reader = new BufferedReader(fileReader);
154
155            String line;
156
157            while ((line = reader.readLine()) != null) {
158                crashReport.append(line);
159                crashReport.append('\n');
160            }
161
162            reader.close();
163        } catch (FileNotFoundException e) {
164            return null;
165        } catch (IOException e) {
166            return null;
167        }
168
169        logFile.delete();
170
171        return crashReport;
172    }
173
174    /**
175     * Displays a notification that TalkBack has crashed and data can be sent to
176     * developers to help improve TalkBack.
177     */
178    private void displayNotification(CharSequence crashReport) {
179        final Context context = mContext.get();
180
181        if (context == null) {
182            return;
183        }
184
185        final Intent emailIntent = new Intent(Intent.ACTION_SEND);
186        emailIntent.setType("plain/text");
187        emailIntent.putExtra(Intent.EXTRA_EMAIL, CRASH_REPORT_EMAILS);
188        emailIntent.putExtra(Intent.EXTRA_SUBJECT,
189                context.getString(R.string.subject_crash_report_email));
190        emailIntent.putExtra(android.content.Intent.EXTRA_TEXT,
191                context.getString(R.string.header_crash_report_email) + crashReport);
192
193        final Notification notification =
194                new Notification(android.R.drawable.stat_notify_error,
195                        context.getString(R.string.title_talkback_crash),
196                        System.currentTimeMillis());
197        notification.setLatestEventInfo(context, context.getString(R.string.title_talkback_crash),
198                context.getString(R.string.message_talkback_crash), PendingIntent.getActivity(
199                        context, 0, emailIntent, PendingIntent.FLAG_UPDATE_CURRENT));
200        notification.defaults |= Notification.DEFAULT_SOUND;
201        notification.flags |= Notification.FLAG_AUTO_CANCEL;
202
203        final NotificationManager manager =
204                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
205        manager.notify(CRASH_NOTIFICATION_ID, notification);
206    }
207}