blob: dc42d5961720982f2ec9f7d25dc2e0da3747da78 [file] [log] [blame]
Pankaj Gargf04dbda2014-10-02 13:52:46 -07001/*
2 * Copyright (c) 2014, The Linux Foundation. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above
10 * copyright notice, this list of conditions and the following
11 * disclaimer in the documentation and/or other materials provided
12 * with the distribution.
13 * * Neither the name of The Linux Foundation nor the names of its
14 * contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
24 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*/
29
30package com.android.browser;
31
32import android.app.Activity;
Pankaj Gargd106f372015-01-20 18:46:09 -080033import android.app.ActivityManager;
Pankaj Gargf04dbda2014-10-02 13:52:46 -070034import android.content.Context;
35import android.os.Build;
36import android.os.Build.VERSION;
37import android.os.SystemClock;
38import android.net.http.AndroidHttpClient;
39import android.util.Log;
Sagar Dhawanc0e1d432015-04-21 17:59:28 -070040import android.os.FileObserver;
41import android.os.Handler;
Pankaj Gargf04dbda2014-10-02 13:52:46 -070042
43import org.codeaurora.swe.BrowserCommandLine;
44
45import org.apache.http.HttpEntity;
46import org.apache.http.HttpResponse;
47import org.apache.http.client.methods.HttpPost;
48import org.apache.http.entity.StringEntity;
49import org.apache.http.client.ClientProtocolException;
Sagar Dhawanc0e1d432015-04-21 17:59:28 -070050import org.apache.http.client.HttpClient;
51import org.apache.http.entity.InputStreamEntity;
52import org.apache.http.impl.client.DefaultHttpClient;
Karthikeyan Periasamybdc07332015-07-17 11:41:59 -070053import org.apache.http.entity.AbstractHttpEntity;
54import org.apache.http.entity.ByteArrayEntity;
55
Pankaj Gargf04dbda2014-10-02 13:52:46 -070056
57import org.json.JSONArray;
58import org.json.JSONObject;
59import org.json.JSONException;
60
61import java.io.File;
62import java.io.FileOutputStream;
63import java.io.FileInputStream;
64import java.io.FileNotFoundException;
65import java.io.IOException;
66import java.io.BufferedReader;
67import java.io.InputStreamReader;
Karthikeyan Periasamybdc07332015-07-17 11:41:59 -070068import java.io.ByteArrayOutputStream;
69import java.io.InputStream;
70import java.io.OutputStream;
Pankaj Gargf04dbda2014-10-02 13:52:46 -070071import java.lang.Integer;
72import java.lang.StringBuilder;
73import java.lang.System;
74import java.lang.Thread.UncaughtExceptionHandler;
75import java.util.Calendar;
Karthikeyan Periasamybdc07332015-07-17 11:41:59 -070076import java.util.zip.GZIPOutputStream;
Pankaj Gargf04dbda2014-10-02 13:52:46 -070077
78public class CrashLogExceptionHandler implements Thread.UncaughtExceptionHandler {
79
80 private static final String CRASH_LOG_FILE = "crash.log";
Pankaj Gargf04dbda2014-10-02 13:52:46 -070081 private static final String CRASH_LOG_MAX_FILE_SIZE_CMD = "crash-log-max-file-size";
Sagar Dhawanc0e1d432015-04-21 17:59:28 -070082 private static final String CRASH_REPORT_DIR = "Crash Reports";
Pankaj Gargf04dbda2014-10-02 13:52:46 -070083
84 private final static String LOGTAG = "CrashLog";
85
86 private Context mAppContext = null;
87
88 private UncaughtExceptionHandler mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
89
90 private String mLogServer = new String();
91
92 private boolean mOverrideHandler = false;
93
94 private int mMaxLogFileSize = 1024 * 1024;
Sagar Dhawanc0e1d432015-04-21 17:59:28 -070095 // To avoid increasing startup time an upload delay is used
96 private static final int UPLOAD_DELAY = 3000;
97
98 private static FileObserver crashObserver;
99
100 private final Handler mCrashReportHandler = new Handler();
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700101
102 public CrashLogExceptionHandler(Context ctx) {
103 mAppContext = ctx;
Tarun Nainanif03c7962015-06-03 11:38:33 -0700104 if (BrowserCommandLine.hasSwitch(BrowserSwitches.CRASH_LOG_SERVER_CMD)) {
Sagar Dhawanc0e1d432015-04-21 17:59:28 -0700105 initNativeReporter(ctx);
Tarun Nainanif03c7962015-06-03 11:38:33 -0700106 mLogServer = BrowserCommandLine.getSwitchValue(BrowserSwitches.CRASH_LOG_SERVER_CMD);
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700107 if (mLogServer != null) {
108 uploadPastCrashLog();
109 mOverrideHandler = true;
110 }
111 }
112
113 try {
Vivek Sekhardcf9d6b2014-12-01 15:08:37 -0800114 int size = Integer.parseInt(
115 BrowserCommandLine.getSwitchValue(CRASH_LOG_MAX_FILE_SIZE_CMD,
116 Integer.toString(mMaxLogFileSize)));
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700117 mMaxLogFileSize = size;
118 } catch (NumberFormatException nfe) {
119 Log.e(LOGTAG,"Max log file size is not configured properly. Using default: "
120 + mMaxLogFileSize);
121 }
122
123 }
124
Sagar Dhawanc0e1d432015-04-21 17:59:28 -0700125 private void initNativeReporter(Context ctx){
126 final File crashReports = new File(ctx.getCacheDir(),CRASH_REPORT_DIR);
127 // On fresh installs, make the directory before registering an observer
128 if (!crashReports.isDirectory()) {
129 crashReports.mkdir();
130 }
131 // Implement FileObserver for crashReports that don't bring the system down
132 crashObserver = new FileObserver(crashReports.getAbsolutePath()) {
133 @Override
134 public void onEvent(int event, String path){
135 if ((event == FileObserver.CREATE) || (event == FileObserver.MOVED_TO)){
136 Log.w(LOGTAG, "A crash report was generated");
137 checkNativeCrash(crashReports);
138 }
139 }
140 };
141 // Native Crash reporting if commandline is set
142 mCrashReportHandler.postDelayed(new Runnable() {
143 @Override
144 public void run() {
145 checkNativeCrash(crashReports);
146 }
147 }, UPLOAD_DELAY);
148 // start watching the crash reports folder
149 crashObserver.startWatching();
150 }
151
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700152 private void saveCrashLog(String crashLog) {
153 // Check if log file exists and it's current size
154 try {
155 File file = new File(mAppContext.getFilesDir(), CRASH_LOG_FILE);
156 if (file.exists()) {
157 if (file.length() > mMaxLogFileSize) {
158 Log.e(LOGTAG,"CRASH Log file size(" + file.length()
159 + ") exceeded max log file size("
160 + mMaxLogFileSize + ")");
161 return;
162 }
163 }
164 } catch (NullPointerException npe) {
165 Log.e(LOGTAG,"Exception while checking file size: " + npe);
166 }
167
168 FileOutputStream crashLogFile = null;
169 try {
170 crashLogFile = mAppContext.openFileOutput(CRASH_LOG_FILE, Context.MODE_APPEND);
171 crashLogFile.write(crashLog.getBytes());
172 } catch(IOException ioe) {
173 Log.e(LOGTAG,"Exception while writing file: " + ioe);
174 } finally {
175 if (crashLogFile != null) {
176 try {
177 crashLogFile.close();
178 } catch (IOException ignore) {
179 }
180 }
181 }
182 }
183
184 private void uploadPastCrashLog() {
185 FileInputStream crashLogFile = null;
186 BufferedReader reader = null;
187 try {
188 crashLogFile = mAppContext.openFileInput(CRASH_LOG_FILE);
189
190 reader = new BufferedReader(new InputStreamReader(crashLogFile));
191 StringBuilder crashLog = new StringBuilder();
192 String line = reader.readLine();
193 if (line != null) {
194 crashLog.append(line);
195 }
196
197 // Typically there's only one line (JSON string) in the crash
198 // log file. This loop would not be executed.
199 while ((line = reader.readLine()) != null) {
200 crashLog.append("\n").append(line);
201 }
202
Sagar Dhawanc0e1d432015-04-21 17:59:28 -0700203 uploadCrashLog(crashLog.toString(), UPLOAD_DELAY);
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700204 } catch(FileNotFoundException fnfe) {
205 Log.v(LOGTAG,"No previous crash found");
206 } catch(IOException ioe) {
207 Log.e(LOGTAG,"Exception while reading crash file: " + ioe);
208 } finally {
209 if (crashLogFile != null) {
210 try {
211 crashLogFile.close();
212 } catch (IOException ignore) {
213 }
214 }
215 if (reader != null) {
216 try {
217 reader.close();
218 } catch (IOException ignore) {
219 }
220 }
221 }
222 }
223
224 private void uploadCrashLog(String data, int after) {
225 final String crashLog = data;
226 final int waitFor = after;
227 new Thread(new Runnable() {
228 public void run(){
229 try {
230 SystemClock.sleep(waitFor);
231 AndroidHttpClient httpClient = AndroidHttpClient.newInstance("Android");;
232 HttpPost httpPost = new HttpPost(mLogServer);
233 HttpEntity se = new StringEntity(crashLog);
234 httpPost.setEntity(se);
235 HttpResponse response = httpClient.execute(httpPost);
236
237 File crashLogFile = new File(mAppContext.getFilesDir(),
238 CRASH_LOG_FILE);
239 if (crashLogFile != null) {
240 crashLogFile.delete();
241 } else {
242 Log.e(LOGTAG,"crash log file could not be opened for deletion");
243 }
244 } catch (ClientProtocolException pe) {
245 Log.e(LOGTAG,"Exception while sending http post: " + pe);
246 } catch (IOException ioe1) {
247 Log.e(LOGTAG,"Exception while sending http post: " + ioe1);
248 }
249 }
250 }).start();
251 }
252
253 public void uncaughtException(Thread t, Throwable e) {
254 if (!mOverrideHandler) {
255 mDefaultHandler.uncaughtException(t, e);
256 return;
257 }
258
259 String crashLog = new String();
260
261 try {
262 Calendar calendar = Calendar.getInstance();
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800263 JSONObject jsonBackTraceObj = new JSONObject();
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700264 String date = calendar.getTime().toString();
265 String aboutSWE = mAppContext.getResources().getString(R.string.about_text);
Pankaj Gargd106f372015-01-20 18:46:09 -0800266 String sweVer = findValueFromAboutText(aboutSWE, "Version: ");
267 String sweHash = findValueFromAboutText(aboutSWE, "Hash: ");
268 String sweBuildDate = findValueFromAboutText(aboutSWE, "Built: ");
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700269
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800270 jsonBackTraceObj.put("date", date);
Pankaj Gargd106f372015-01-20 18:46:09 -0800271 jsonBackTraceObj.put("android-model", android.os.Build.MODEL);
272 jsonBackTraceObj.put("android-device", android.os.Build.DEVICE);
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800273 jsonBackTraceObj.put("android-ver", android.os.Build.VERSION.RELEASE);
274 jsonBackTraceObj.put("browser-ver", sweVer);
275 jsonBackTraceObj.put("browser-hash", sweHash);
276 jsonBackTraceObj.put("browser-build-date", sweBuildDate);
277 jsonBackTraceObj.put("thread", t.toString());
Pankaj Gargd106f372015-01-20 18:46:09 -0800278 jsonBackTraceObj.put("format", "crashmon-1");
279 jsonBackTraceObj.put("monkey-test", ActivityManager.isUserAMonkey());
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700280
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800281 JSONArray jsonStackArray = new JSONArray();
282
283 Throwable throwable = e;
284 String stackTag = "Exception thrown while running";
285 while (throwable != null) {
286 JSONObject jsonStackObj = new JSONObject();
287 StackTraceElement[] arr = throwable.getStackTrace();
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700288 JSONArray jsonStack = new JSONArray(arr);
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800289
290 jsonStackObj.put("cause", throwable.getCause());
291 jsonStackObj.put("message", throwable.getMessage());
292 jsonStackObj.put(stackTag, jsonStack);
293
294 jsonStackArray.put(jsonStackObj);
295
296 stackTag = "stack";
297 throwable = throwable.getCause();
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700298 }
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800299 jsonBackTraceObj.put("exceptions", jsonStackArray);
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700300
301 JSONObject jsonMainObj = new JSONObject();
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800302 jsonMainObj.put("backtraces", jsonBackTraceObj);
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700303
304 Log.e(LOGTAG, "Exception: " + jsonMainObj.toString(4));
305 crashLog = jsonMainObj.toString();
306
307 } catch (JSONException je) {
308 Log.e(LOGTAG, "Failed in JSON encoding: " + je);
309 }
310
311 saveCrashLog(crashLog);
312
313 uploadCrashLog(crashLog, 0);
314
315 mDefaultHandler.uncaughtException(t, e);
316 }
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800317
318 private String findValueFromAboutText(String aboutText, String aboutKey) {
319 int start = aboutText.indexOf(aboutKey);
320 int end = aboutText.indexOf("\n", start);
321 String value = "";
322
323 if (start != -1 && end != -1) {
324 start += aboutKey.length();
325 value = aboutText.substring(start, end);
326 }
327 return value;
328 }
329
Sagar Dhawanc0e1d432015-04-21 17:59:28 -0700330 private void checkNativeCrash(final File crashReportsDir) {
331 // Search cache/Crash Reports/ for any crashes
332 if (crashReportsDir.exists()) {
333 new Thread(new Runnable() {
334 @Override
335 public void run() {
336 for (File f : crashReportsDir.listFiles()) {
337 uploadNativeCrashReport(f);
338 }
339 }
340 }).start();
341 }
342 }
343
344 private void uploadNativeCrashReport(final File report) {
345 Log.w(LOGTAG, "Preparing Crash Report for upload " + report.getName());
346 // get server url from commandline
Tarun Nainanif03c7962015-06-03 11:38:33 -0700347 String server = BrowserCommandLine.getSwitchValue(BrowserSwitches.CRASH_LOG_SERVER_CMD);
Sagar Dhawanc0e1d432015-04-21 17:59:28 -0700348 try {
349 HttpClient httpClient = new DefaultHttpClient();
350 HttpPost httpPost = new HttpPost(server);
Karthikeyan Periasamybdc07332015-07-17 11:41:59 -0700351
352 // Compress the data
353 ByteArrayOutputStream arrayStream = new ByteArrayOutputStream();
354 OutputStream gzipData = new GZIPOutputStream(arrayStream);
355 InputStream inputStream = new FileInputStream(report);
356 long length = report.length();
357 byte[] data = new byte[(int)length];
358
359 // Read in the bytes
360 int offset = 0;
361 int numRead = 0;
362 while (offset < data.length
363 && (numRead=inputStream.read(data, offset, data.length-offset)) >= 0) {
364 offset += numRead;
365 }
366 gzipData.write(data);
367 gzipData.close();
368
369 AbstractHttpEntity entity = new ByteArrayEntity(arrayStream.toByteArray());
370
371 // Send the report as a compressed Binary
372 entity.setContentType("binary/octet-stream");
373 entity.setContentEncoding("gzip");
374 entity.setChunked(false);
375 httpPost.setEntity(entity);
376
Sagar Dhawanc0e1d432015-04-21 17:59:28 -0700377 HttpResponse response = httpClient.execute(httpPost);
378 int status = response.getStatusLine().getStatusCode();
379 if (status == 200)
380 report.delete();
381 else Log.w(LOGTAG, "Upload Failure. Will try again next time- " + status);
382
383 } catch (Exception e) {
384 Log.w(LOGTAG, "Crash Report failed to upload, will try again next time " + e);
385 }
386 }
387
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700388}