1 /* -*- Mode: Java; tab-width: 20; indent-tabs-mode: nil; -*-
2 * ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is Mozilla Android code.
17 * The Initial Developer of the Original Code is Mozilla Foundation.
18 * Portions created by the Initial Developer are Copyright (C) 2010
19 * the Initial Developer. All Rights Reserved.
22 * Brad Lassey <blassey@mozilla.com>
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
39 package org
.mozilla
.@MOZ_APP_NAME@
;
43 import android
.content
.*;
45 import android
.util
.*;
46 import android
.view
.*;
47 import android
.view
.View
.*;
48 import android
.widget
.*;
50 import org
.mozilla
.gecko
.*;
54 import java
.nio
.channels
.*;
56 public class CrashReporter
extends Activity
58 static final String kMiniDumpPathKey
= "upload_file_minidump";
59 static final String kPageURLKey
= "URL";
60 static final String kNotesKey
= "Notes";
61 ProgressDialog mProgressDialog
;
62 File mPendingMinidumpFile
;
63 File mPendingExtrasFile
;
64 HashMap
<String
, String
> mExtrasStringMap
;
66 boolean moveFile(File inFile
, File outFile
)
68 Log
.i("GeckoCrashReporter", "moving " + inFile
+ " to " + outFile
);
69 if (inFile
.renameTo(outFile
))
72 outFile
.createNewFile();
73 Log
.i("GeckoCrashReporter", "couldn't rename minidump file");
75 FileChannel inChannel
= new FileInputStream(inFile
).getChannel();
76 FileChannel outChannel
= new FileOutputStream(outFile
).getChannel();
77 long transferred
= inChannel
.transferTo(0, inChannel
.size(), outChannel
);
83 } catch (Exception e
) {
84 Log
.e("GeckoCrashReporter",
85 "exception while copying minidump file: ", e
);
94 mProgressDialog
.dismiss();
99 public void onCreate(Bundle savedInstanceState
)
101 super.onCreate(savedInstanceState
);
102 setContentView(R
.layout
.crash_reporter
);
103 mProgressDialog
= new ProgressDialog(CrashReporter
.this);
104 mProgressDialog
.setMessage(getString(R
.string
.sending_crash_report
));
106 final Button restartButton
= (Button
) findViewById(R
.id
.restart
);
107 final Button closeButton
= (Button
) findViewById(R
.id
.close
);
108 String passedMinidumpPath
= getIntent().getStringExtra("minidumpPath");
109 File passedMinidumpFile
= new File(passedMinidumpPath
);
111 new File("/data/data/org.mozilla.@MOZ_APP_NAME@/mozilla/Crash Reports/pending");
113 mPendingMinidumpFile
= new File(pendingDir
, passedMinidumpFile
.getName());
114 moveFile(passedMinidumpFile
, mPendingMinidumpFile
);
116 File extrasFile
= new File(passedMinidumpPath
.replaceAll(".dmp", ".extra"));
117 mPendingExtrasFile
= new File(pendingDir
, extrasFile
.getName());
118 moveFile(extrasFile
, mPendingExtrasFile
);
120 mExtrasStringMap
= new HashMap
<String
, String
>();
121 readStringsFromFile(mPendingExtrasFile
.getPath(), mExtrasStringMap
);
124 public void onCloseClick(View v
)
126 mProgressDialog
.show();
127 new Thread(new Runnable() {
129 sendReport(mPendingMinidumpFile
, mExtrasStringMap
, mPendingExtrasFile
);
133 public void onRestartClick(View v
)
136 mProgressDialog
.show();
137 new Thread(new Runnable() {
139 sendReport(mPendingMinidumpFile
, mExtrasStringMap
, mPendingExtrasFile
);
143 boolean readStringsFromFile(String filePath
, Map stringMap
)
146 BufferedReader reader
= new BufferedReader(
147 new FileReader(filePath
));
148 return readStringsFromReader(reader
, stringMap
);
149 } catch (Exception e
) {
150 Log
.e("GeckoCrashReporter", "exception while reading strings: ", e
);
155 boolean readStringsFromReader(BufferedReader reader
, Map stringMap
)
156 throws java
.io
.IOException
159 while ((line
= reader
.readLine()) != null) {
161 if ((equalsPos
= line
.indexOf('=')) != -1) {
162 String key
= line
.substring(0, equalsPos
);
163 String val
= unescape(line
.substring(equalsPos
+ 1));
164 stringMap
.put(key
, val
);
171 String
generateBoundary()
173 // Generate some random numbers to fill out the boundary
174 int r0
= (int)((double)Integer
.MAX_VALUE
* Math
.random());
175 int r1
= (int)((double)Integer
.MAX_VALUE
* Math
.random());
177 return String
.format("---------------------------%08X%08X", r0
, r1
);
180 void sendPart(OutputStream os
, String boundary
, String name
, String data
)
183 os
.write(("--" + boundary
+ "\r\n" +
184 "Content-Disposition: form-data; name=\"" +
185 name
+ "\"\r\n\r\n" +
186 data
+ "\r\n").getBytes());
189 void sendFile(OutputStream os
, String boundary
, String name
, File file
)
192 os
.write(("--" + boundary
+ "\r\n" +
193 "Content-Disposition: form-data; " +
194 "name=\"" + name
+ "\"; " +
195 "filename=\"" + file
.getName() + "\"\r\n" +
196 "Content-Type: application/octet-stream\r\n" +
199 new FileInputStream(file
).getChannel();
200 fc
.transferTo(0, fc
.size(), Channels
.newChannel(os
));
204 void sendReport(File minidumpFile
, Map
<String
, String
> extras
,
207 Log
.i("GeckoCrashReport", "sendReport: " + minidumpFile
.getPath());
208 final CheckBox sendReportCheckbox
= (CheckBox
) findViewById(R
.id
.send_report
);
209 final CheckBox includeURLCheckbox
= (CheckBox
) findViewById(R
.id
.include_url
);
211 if (!sendReportCheckbox
.isChecked())
214 String spec
= extras
.get("ServerURL");
218 Log
.i("GeckoCrashReport", "server url: " + spec
);
220 URL url
= new URL(spec
);
221 HttpURLConnection conn
= (HttpURLConnection
)url
.openConnection();
222 conn
.setRequestMethod("POST");
223 String boundary
= generateBoundary();
224 conn
.setDoOutput(true);
225 conn
.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary
);
227 OutputStream os
= conn
.getOutputStream();
228 Iterator
<String
> keys
= extras
.keySet().iterator();
229 while (keys
.hasNext()) {
230 String key
= keys
.next();
231 if (key
.equals(kPageURLKey
)) {
232 if (includeURLCheckbox
.isChecked())
233 sendPart(os
, boundary
, key
, extras
.get(key
));
234 } else if (!key
.equals("ServerURL") && !key
.equals(kNotesKey
)) {
235 sendPart(os
, boundary
, key
, extras
.get(key
));
239 // Add some extra information to notes so its displayed by
240 // crash-stats.mozilla.org. Remove this when bug 607942 is fixed.
241 String notes
= extras
.containsKey(kNotesKey
) ? extras
.get(kNotesKey
) +
243 if (@MOZ_MIN_CPU_VERSION@
< 7)
244 notes
+= "nothumb Build\n";
245 notes
+= Build
.MANUFACTURER
+ " ";
246 notes
+= Build
.MODEL
+ "\n";
247 notes
+= Build
.FINGERPRINT
;
248 sendPart(os
, boundary
, kNotesKey
, notes
);
250 sendPart(os
, boundary
, "Min_ARM_Version", "@MOZ_MIN_CPU_VERSION@");
251 sendPart(os
, boundary
, "Android_Manufacturer", Build
.MANUFACTURER
);
252 sendPart(os
, boundary
, "Android_Model", Build
.MODEL
);
253 sendPart(os
, boundary
, "Android_Board", Build
.BOARD
);
254 sendPart(os
, boundary
, "Android_Brand", Build
.BRAND
);
255 sendPart(os
, boundary
, "Android_CPU_ABI", Build
.CPU_ABI
);
256 sendPart(os
, boundary
, "Android_CPU_ABI2", Build
.CPU_ABI2
);
257 sendPart(os
, boundary
, "Android_Device", Build
.DEVICE
);
258 sendPart(os
, boundary
, "Android_Display", Build
.DISPLAY
);
259 sendPart(os
, boundary
, "Android_Fingerprint", Build
.FINGERPRINT
);
260 sendPart(os
, boundary
, "Android_Hardware", Build
.HARDWARE
);
261 sendPart(os
, boundary
, "Android_Version", Build
.VERSION
.SDK_INT
+ " (" + Build
.VERSION
.CODENAME
+ ")");
263 sendFile(os
, boundary
, kMiniDumpPathKey
, minidumpFile
);
264 os
.write(("\r\n--" + boundary
+ "--\r\n").getBytes());
267 BufferedReader br
= new BufferedReader(
268 new InputStreamReader(conn
.getInputStream()));
269 HashMap
<String
, String
> responseMap
= new HashMap
<String
, String
>();
270 readStringsFromReader(br
, responseMap
);
272 if (conn
.getResponseCode() == conn
.HTTP_OK
) {
273 File submittedDir
= new File(
274 "/data/data/org.mozilla.@MOZ_APP_NAME@/mozilla/Crash Reports/submitted");
275 submittedDir
.mkdirs();
276 minidumpFile
.delete();
278 String crashid
= responseMap
.get("CrashID");
279 File file
= new File(submittedDir
, crashid
+ ".txt");
280 FileOutputStream fos
= new FileOutputStream(file
);
281 fos
.write("Crash ID: ".getBytes());
282 fos
.write(crashid
.getBytes());
285 } catch (IOException e
) {
286 Log
.e("GeckoCrashReporter", "exception during send: ", e
);
295 String action
= "android.intent.action.MAIN";
296 Intent intent
= new Intent(action
);
297 intent
.setClassName("org.mozilla.@MOZ_APP_NAME@",
298 "org.mozilla.@MOZ_APP_NAME@.App");
299 intent
.setFlags(Intent
.FLAG_ACTIVITY_NEW_TASK
);
300 Log
.i("GeckoCrashReporter", intent
.toString());
301 startActivity(intent
);
302 } catch (Exception e
) {
303 Log
.e("GeckoCrashReporter", "error while trying to restart", e
);
307 public String
unescape(String string
)
309 return string
.replaceAll("\\\\", "\\").replaceAll("\\n", "\n")
310 .replaceAll("\\t", "\t");