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