/TalkBack/src/com/google/android/marvin/talkback/TalkBackExceptionHandler.java
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}