bug 595169 - Implement crash reporter client for Android r=mwu,ted ui-r=madhava a=blocking-fennec
authorBrad Lassey <blassey@mozilla.com>
Fri, 15 Oct 2010 23:18:07 -0400
changeset 56280 13dc6bfc5e7a578984cdbecdbe2e8253326a3525
parent 56279 1f000e6c5237dffab00563f64ea75456af6cbb5a
child 56281 d54d195f468fa0a16bbfc34948b9ad848ac69a15
push idunknown
push userunknown
push dateunknown
reviewersmwu, ted, madhava, blocking-fennec
bugs595169
milestone2.0b8pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 595169 - Implement crash reporter client for Android r=mwu,ted ui-r=madhava a=blocking-fennec
embedding/android/AndroidManifest.xml.in
embedding/android/CrashReporter.java.in
embedding/android/Makefile.in
embedding/android/android_strings.dtd
embedding/android/resources/drawable/crash_reporter.png
embedding/android/resources/layout/crash_reporter.xml
embedding/android/strings.xml.in
--- a/embedding/android/AndroidManifest.xml.in
+++ b/embedding/android/AndroidManifest.xml.in
@@ -72,10 +72,20 @@
             </intent-filter>
         </activity>
 
         <receiver android:enabled="true" android:name="Restarter">
           <intent-filter>
             <action android:name="org.mozilla.gecko.restart@MOZ_APP_NAME@" />
           </intent-filter>
         </receiver>
+#if MOZ_CRASHREPORTER
+	<activity android:name="CrashReporter"
+                  android:label="@MOZ_APP_DISPLAYNAME@ Crash Reporter"
+		  android:icon="@drawable/crash_reporter"
+		  android:process="org.mozilla.@MOZ_APP_NAME@.crashReporter" >
+          <intent-filter>
+            <action android:name="org.mozilla.gecko.reportCrash" />
+          </intent-filter>
+	</activity>
+#endif
     </application>
 </manifest> 
new file mode 100644
--- /dev/null
+++ b/embedding/android/CrashReporter.java.in
@@ -0,0 +1,285 @@
+/* -*- Mode: Java; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brad Lassey <blassey@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#filter substitution
+package org.mozilla.@MOZ_APP_NAME@;
+
+
+import android.app.*;
+import android.content.*;
+import android.os.*;
+import android.util.*;
+import android.view.*;
+import android.view.View.*;
+import android.widget.*;
+
+import org.mozilla.gecko.*;
+import java.util.*;
+import java.io.*;
+import java.net.*;
+import java.nio.channels.*;
+
+public class CrashReporter extends Activity
+{
+  static final String kMiniDumpPathKey = "upload_file_minidump";
+  static final String kPageURLKey = "URL";
+  ProgressDialog mProgressDialog;
+  File mPendingMinidumpFile;
+  File mPendingExtrasFile;
+  HashMap<String, String> mExtrasStringMap;
+
+  boolean moveFile(File inFile, File outFile)
+  {
+    Log.i("GeckoCrashReporter", "moving " + inFile + " to " + outFile);
+    if (inFile.renameTo(outFile))
+      return true;
+    try {
+      outFile.createNewFile();
+      Log.i("GeckoCrashReporter", "couldn't rename minidump file");
+      // so copy it instead
+      FileChannel inChannel = new FileInputStream(inFile).getChannel();
+      FileChannel outChannel = new FileOutputStream(outFile).getChannel();
+      long transferred = inChannel.transferTo(0, inChannel.size(), outChannel);
+      inChannel.close();
+      outChannel.close();
+
+      if (transferred > 0)
+        inFile.delete();
+    } catch (Exception e) {
+      Log.e("GeckoCrashReporter",
+            "exception while copying minidump file: ", e);
+      return false;
+    }
+    return true;
+  }
+
+  @Override
+  public void finish()
+  {
+    mProgressDialog.dismiss();
+    super.finish();
+  }
+
+  @Override
+  public void onCreate(Bundle savedInstanceState)
+  {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.crash_reporter);
+    mProgressDialog = new ProgressDialog(CrashReporter.this);
+    mProgressDialog.setMessage(getString(R.string.sending_crash_report));
+
+    final Button restartButton = (Button) findViewById(R.id.restart);
+    final Button closeButton = (Button) findViewById(R.id.close);
+    String passedMinidumpPath = getIntent().getStringExtra("minidumpPath");
+    File passedMinidumpFile = new File(passedMinidumpPath);
+    File pendingDir =
+      new File("/data/data/org.mozilla.@MOZ_APP_NAME@/mozilla/Crash Reports/pending");
+    pendingDir.mkdirs();
+    mPendingMinidumpFile = new File(pendingDir, passedMinidumpFile.getName());
+    moveFile(passedMinidumpFile, mPendingMinidumpFile);
+
+    File extrasFile = new File(passedMinidumpPath.replaceAll(".dmp", ".extra"));
+    mPendingExtrasFile = new File(pendingDir, extrasFile.getName());
+    moveFile(extrasFile, mPendingExtrasFile);
+
+    mExtrasStringMap = new HashMap<String, String>();
+    readStringsFromFile(mPendingExtrasFile.getPath(), mExtrasStringMap);
+  }
+
+  public void onCloseClick(View v)
+  {
+    mProgressDialog.show();
+    new Thread(new Runnable() {
+      public void run() {
+        sendReport(mPendingMinidumpFile, mExtrasStringMap, mPendingExtrasFile);
+      }}).start();
+  }
+
+  public void onRestartClick(View v)
+  {
+    doRestart();
+    mProgressDialog.show();
+    new Thread(new Runnable() {
+      public void run() {
+        sendReport(mPendingMinidumpFile, mExtrasStringMap, mPendingExtrasFile);
+      }}).start();
+  }
+
+  boolean readStringsFromFile(String filePath, Map stringMap)
+  {
+    try {
+      BufferedReader reader = new BufferedReader(
+        new FileReader(filePath));
+      return readStringsFromReader(reader, stringMap);
+    } catch (Exception e) {
+      Log.e("GeckoCrashReporter", "exception while reading strings: ", e);
+      return false;
+    }
+  }
+
+  boolean readStringsFromReader(BufferedReader reader, Map stringMap)
+    throws java.io.IOException
+  {
+    String line;
+    while ((line = reader.readLine()) != null) {
+      int equalsPos = -1;
+      if ((equalsPos = line.indexOf('=')) != -1) {
+        String key = line.substring(0, equalsPos);
+        String val = unescape(line.substring(equalsPos + 1));
+        stringMap.put(key, val);
+      }
+    }
+    reader.close();
+    return true;
+  }
+
+  String generateBoundary()
+  {
+    // Generate some random numbers to fill out the boundary
+    int r0 = (int)((double)Integer.MAX_VALUE * Math.random());
+    int r1 = (int)((double)Integer.MAX_VALUE * Math.random());
+
+    return String.format("---------------------------%08X%08X", r0, r1);
+  }
+
+  void sendPart(OutputStream os, String boundary, String name, String data)
+    throws IOException
+  {
+    os.write(("--" + boundary + "\r\n" +
+              "Content-Disposition: form-data; name=\"" +
+              name + "\"\r\n\r\n" +
+              data + "\r\n").getBytes());
+  }
+
+  void sendFile(OutputStream os, String boundary, String name, File file)
+    throws IOException
+  {
+    os.write(("--" + boundary + "\r\n" +
+              "Content-Disposition: form-data; " +
+              "name=\"" + name + "\"; " +
+              "filename=\"" + file.getName() + "\"\r\n" +
+              "Content-Type: application/octet-stream\r\n" +
+              "\r\n").getBytes());
+    FileChannel fc =
+      new FileInputStream(file).getChannel();
+    fc.transferTo(0, fc.size(), Channels.newChannel(os));
+    fc.close();
+  }
+
+  void sendReport(File minidumpFile, Map<String, String> extras,
+                  File extrasFile)
+  {
+    Log.i("GeckoCrashReport", "sendReport: " + minidumpFile.getPath());
+    final CheckBox sendReportCheckbox = (CheckBox) findViewById(R.id.send_report);
+    final CheckBox includeURLCheckbox = (CheckBox) findViewById(R.id.include_url);
+
+    if (!sendReportCheckbox.isChecked())
+      finish();
+
+    String spec = extras.get("ServerURL");
+    if (spec == null)
+      finish();
+
+    Log.i("GeckoCrashReport", "server url: " + spec);
+    try {
+      URL url = new URL(spec);
+      HttpURLConnection conn = (HttpURLConnection)url.openConnection();
+      conn.setRequestMethod("POST");
+      String boundary = generateBoundary();
+      conn.setDoOutput(true);
+      conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
+
+      OutputStream os = conn.getOutputStream();
+      Iterator<String> keys = extras.keySet().iterator();
+      while (keys.hasNext()) {
+        String key = keys.next();
+        if (key.equals(kPageURLKey)) {
+          if (includeURLCheckbox.isChecked())
+            sendPart(os, boundary, key, extras.get(key));
+        } else if (!key.equals("ServerURL")){
+          sendPart(os, boundary, key, extras.get(key));
+        }
+      }
+      sendFile(os, boundary, kMiniDumpPathKey, minidumpFile);
+      os.write(("\r\n--" + boundary + "--\r\n").getBytes());
+      os.flush();
+      os.close();
+      BufferedReader br = new BufferedReader(
+        new InputStreamReader(conn.getInputStream()));
+      HashMap<String, String>  responseMap = new HashMap<String, String>();
+      readStringsFromReader(br, responseMap);
+
+      if (conn.getResponseCode() == conn.HTTP_OK) {
+        File submittedDir = new File(
+          "/data/data/org.mozilla.@MOZ_APP_NAME@/mozilla/Crash Reports/submitted");
+        submittedDir.mkdirs();
+        minidumpFile.delete();
+        extrasFile.delete();
+        String crashid = responseMap.get("CrashID");
+        File file = new File(submittedDir, crashid + ".txt");
+        FileOutputStream fos = new FileOutputStream(file);
+        fos.write("Crash ID: ".getBytes());
+        fos.write(crashid.getBytes());
+        fos.close();
+      }
+    } catch (IOException e) {
+      Log.e("GeckoCrashReporter", "exception during send: ", e);
+    }
+
+    finish();
+  }
+
+  void doRestart()
+  {
+    try {
+      String action = "org.mozilla.gecko.restart@MOZ_APP_NAME@";
+      String amCmd = "/system/bin/am broadcast -a " + action +
+        " -n org.mozilla.@MOZ_APP_NAME@/org.mozilla.@MOZ_APP_NAME@.Restarter";
+      Log.i("GeckoCrashReporter", amCmd);
+      Runtime.getRuntime().exec(amCmd);
+    } catch (Exception e) {
+      Log.i("GeckoCrashReporter", e.toString());
+    }
+  }
+
+  public String unescape(String string)
+  {
+    return string.replaceAll("\\\\", "\\").replaceAll("\\n", "\n")
+      .replaceAll("\\t", "\t");
+  }
+}
+
--- a/embedding/android/Makefile.in
+++ b/embedding/android/Makefile.in
@@ -66,48 +66,56 @@ MIN_CPU_VERSION=5
 endif
 
 DEFINES += \
   -DMOZ_APP_DISPLAYNAME=$(MOZ_APP_DISPLAYNAME) \
   -DMOZ_APP_NAME=$(MOZ_APP_NAME) \
   -DMOZ_APP_VERSION=$(MOZ_APP_VERSION) \
   -DMOZ_CHILD_PROCESS_NAME=$(MOZ_CHILD_PROCESS_NAME) \
   -DMOZ_MIN_CPU_VERSION=$(MIN_CPU_VERSION) \
+  -DMOZ_CRASHREPORTER=$(MOZ_CRASHREPORTER) \
   $(NULL)
 
 GARBAGE += \
   AndroidManifest.xml  \
   classes.dex  \
   $(PROCESSEDJAVAFILES) \
   gecko.ap_  \
   res/values/strings.xml \
   R.java \
   $(NULL)
 
-GARBAGE_DIRS += classes
+GARBAGE_DIRS += classes res
 
 # Bug 567884 - Need a way to find appropriate icons during packaging
 ifeq ($(MOZ_APP_NAME),fennec)
 ICON_PATH = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_48x48.png
 ICON_PATH_HDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_72x72.png
 else
 ICON_PATH = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon48.png
 ICON_PATH_HDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon64.png
 endif
 
 RES_LAYOUT = \
   res/layout/notification_progress.xml \
-  res/layout/notification_progress_text.xml
+  res/layout/notification_progress_text.xml \
   $(NULL)
 
 JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar
 
 DEFAULT_BRANDPATH = $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales/en-US/brand.dtd
 DEFAULT_STRINGSPATH = android_strings.dtd
 
+ifdef MOZ_CRASHREPORTER
+PROCESSEDJAVAFILES += CrashReporter.java
+MOZ_ANDROID_DRAWABLES += embedding/android/resources/drawable/crash_reporter.png
+RES_LAYOUT += res/layout/crash_reporter.xml
+endif
+
+
 include $(topsrcdir)/config/rules.mk
 
 # Override the Java settings with some specific android settings
 include $(topsrcdir)/config/android-common.mk
 
 # Note that we're going to set up a dependency directly between embed_android.dex and the java files
 # Instead of on the .class files, since more than one .class file might be produced per .java file
 classes.dex: $(JAVAFILES) $(PROCESSEDJAVAFILES) R.java
@@ -122,23 +130,21 @@ AndroidManifest.xml $(PROCESSEDJAVAFILES
 res/drawable/icon.png: $(MOZ_APP_ICON)
 	$(NSINSTALL) -D res/drawable
 	cp $(ICON_PATH) $@
 
 res/drawable-hdpi/icon.png: $(MOZ_APP_ICON)
 	$(NSINSTALL) -D res/drawable-hdpi
 	cp $(ICON_PATH_HDPI) $@
 
-ifdef MOZ_ANDROID_DRAWABLES
 RES_DRAWABLE = $(addprefix res/drawable/,$(notdir $(MOZ_ANDROID_DRAWABLES)))
 
 $(RES_DRAWABLE): $(addprefix $(topsrcdir)/,$(MOZ_ANDROID_DRAWABLES))
 	$(NSINSTALL) -D res/drawable
 	$(NSINSTALL) $^ res/drawable/
-endif
 
 $(RES_LAYOUT): $(subst res/,$(srcdir)/resources/,$(RES_LAYOUT))
 	$(NSINSTALL) -D res/layout
 	$(NSINSTALL) $(srcdir)/resources/layout/* res/layout/
 
 R.java: $(MOZ_APP_ICON) $(RES_LAYOUT) $(RES_DRAWABLE) res/values/strings.xml $(LOCALIZED_STRINGS_XML) AndroidManifest.xml
 	$(AAPT) package -f -M AndroidManifest.xml -I $(ANDROID_SDK)/android.jar -S res -J . --custom-package org.mozilla.gecko
 
--- a/embedding/android/android_strings.dtd
+++ b/embedding/android/android_strings.dtd
@@ -1,4 +1,11 @@
 <!ENTITY  splash_screen_label "&brandShortName; is loading">
 <!ENTITY  incompatable_cpu_error "This device does not meet the minimum system requirements for &brandShortName;.">
 <!ENTITY  no_space_to_start_error "There is not enough space available for &brandShortName; to start.">
 <!ENTITY  error_loading_file "An error occurred when trying to load files required to run &brandShortName;">
+<!ENTITY  crash_message "&brandShortName; has crashed. Your tabs should be listed on the &brandShortName; Start page when you restart.">
+<!ENTITY  crash_help_message "Please help us fix this problem!">
+<!ENTITY  crash_send_report_message "Send Mozilla a crash report">
+<!ENTITY  crash_include_url "Include page address">
+<!ENTITY  crash_close_label "Close">
+<!ENTITY  crash_restart_label "Restart &brandShortName;">
+<!ENTITY  sending_crash_report "Sending crash report\u2026">
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5804e08a772e010f0c986460898fa18f321c9311
GIT binary patch
literal 2860
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
KMNUMnLSTY|v0hLB
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/layout/crash_reporter.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:orientation="vertical"
+              android:padding="10px" >
+  <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="10px"
+            android:textStyle="bold"
+            android:text="@string/crash_message"/>
+  <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="10px"
+            android:text="@string/crash_help_message"/>
+  <CheckBox android:id="@+id/send_report"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:checked="true"
+            android:text="@string/crash_send_report_message" />
+  <CheckBox android:id="@+id/include_url"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/crash_include_url" />
+  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:padding="10px"
+                android:gravity="center_horizontal" >
+    <Button android:id="@+id/close"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="10px"
+            android:minWidth="120sp"
+	    android:onClick="onCloseClick"
+            android:text="@string/crash_close_label" />
+    <Button android:id="@+id/restart"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="10px"
+            android:minWidth="120sp"
+	    android:onClick="onRestartClick"
+            android:text="@string/crash_restart_label" />
+  </LinearLayout>
+</LinearLayout>
--- a/embedding/android/strings.xml.in
+++ b/embedding/android/strings.xml.in
@@ -4,9 +4,16 @@
 #includesubst @BRANDPATH@
 #includesubst @STRINGSPATH@
 ]>
 <resources>
   <string name="splash_screen_label">&splash_screen_label;</string>
   <string name="incompatable_cpu_error">&incompatable_cpu_error;</string>
   <string name="no_space_to_start_error">&no_space_to_start_error;</string>
   <string name="error_loading_file">&error_loading_file;</string>
+  <string name="crash_message">&crash_message;</string>
+  <string name="crash_help_message">&crash_help_message;</string>
+  <string name="crash_send_report_message">&crash_send_report_message;</string>
+  <string name="crash_include_url">&crash_include_url;</string>
+  <string name="crash_close_label">&crash_close_label;</string>
+  <string name="crash_restart_label">&crash_restart_label;</string>
+  <string name="sending_crash_report">&sending_crash_report;</string>
 </resources>