blob: 367a4099023a15149b685d1ebe226d0e87979b42 [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;
Pankaj Gargf04dbda2014-10-02 13:52:46 -070053
54import org.json.JSONArray;
55import org.json.JSONObject;
56import org.json.JSONException;
57
58import java.io.File;
59import java.io.FileOutputStream;
60import java.io.FileInputStream;
61import java.io.FileNotFoundException;
62import java.io.IOException;
63import java.io.BufferedReader;
64import java.io.InputStreamReader;
65import java.lang.Integer;
66import java.lang.StringBuilder;
67import java.lang.System;
68import java.lang.Thread.UncaughtExceptionHandler;
69import java.util.Calendar;
70
71public class CrashLogExceptionHandler implements Thread.UncaughtExceptionHandler {
72
73 private static final String CRASH_LOG_FILE = "crash.log";
Pankaj Gargf04dbda2014-10-02 13:52:46 -070074 private static final String CRASH_LOG_MAX_FILE_SIZE_CMD = "crash-log-max-file-size";
Sagar Dhawanc0e1d432015-04-21 17:59:28 -070075 private static final String CRASH_REPORT_DIR = "Crash Reports";
Pankaj Gargf04dbda2014-10-02 13:52:46 -070076
77 private final static String LOGTAG = "CrashLog";
78
79 private Context mAppContext = null;
80
81 private UncaughtExceptionHandler mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
82
83 private String mLogServer = new String();
84
85 private boolean mOverrideHandler = false;
86
87 private int mMaxLogFileSize = 1024 * 1024;
Sagar Dhawanc0e1d432015-04-21 17:59:28 -070088 // To avoid increasing startup time an upload delay is used
89 private static final int UPLOAD_DELAY = 3000;
90
91 private static FileObserver crashObserver;
92
93 private final Handler mCrashReportHandler = new Handler();
Pankaj Gargf04dbda2014-10-02 13:52:46 -070094
95 public CrashLogExceptionHandler(Context ctx) {
96 mAppContext = ctx;
Tarun Nainanif03c7962015-06-03 11:38:33 -070097 if (BrowserCommandLine.hasSwitch(BrowserSwitches.CRASH_LOG_SERVER_CMD)) {
Sagar Dhawanc0e1d432015-04-21 17:59:28 -070098 initNativeReporter(ctx);
Tarun Nainanif03c7962015-06-03 11:38:33 -070099 mLogServer = BrowserCommandLine.getSwitchValue(BrowserSwitches.CRASH_LOG_SERVER_CMD);
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700100 if (mLogServer != null) {
101 uploadPastCrashLog();
102 mOverrideHandler = true;
103 }
104 }
105
106 try {
Vivek Sekhardcf9d6b2014-12-01 15:08:37 -0800107 int size = Integer.parseInt(
108 BrowserCommandLine.getSwitchValue(CRASH_LOG_MAX_FILE_SIZE_CMD,
109 Integer.toString(mMaxLogFileSize)));
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700110 mMaxLogFileSize = size;
111 } catch (NumberFormatException nfe) {
112 Log.e(LOGTAG,"Max log file size is not configured properly. Using default: "
113 + mMaxLogFileSize);
114 }
115
116 }
117
Sagar Dhawanc0e1d432015-04-21 17:59:28 -0700118 private void initNativeReporter(Context ctx){
119 final File crashReports = new File(ctx.getCacheDir(),CRASH_REPORT_DIR);
120 // On fresh installs, make the directory before registering an observer
121 if (!crashReports.isDirectory()) {
122 crashReports.mkdir();
123 }
124 // Implement FileObserver for crashReports that don't bring the system down
125 crashObserver = new FileObserver(crashReports.getAbsolutePath()) {
126 @Override
127 public void onEvent(int event, String path){
128 if ((event == FileObserver.CREATE) || (event == FileObserver.MOVED_TO)){
129 Log.w(LOGTAG, "A crash report was generated");
130 checkNativeCrash(crashReports);
131 }
132 }
133 };
134 // Native Crash reporting if commandline is set
135 mCrashReportHandler.postDelayed(new Runnable() {
136 @Override
137 public void run() {
138 checkNativeCrash(crashReports);
139 }
140 }, UPLOAD_DELAY);
141 // start watching the crash reports folder
142 crashObserver.startWatching();
143 }
144
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700145 private void saveCrashLog(String crashLog) {
146 // Check if log file exists and it's current size
147 try {
148 File file = new File(mAppContext.getFilesDir(), CRASH_LOG_FILE);
149 if (file.exists()) {
150 if (file.length() > mMaxLogFileSize) {
151 Log.e(LOGTAG,"CRASH Log file size(" + file.length()
152 + ") exceeded max log file size("
153 + mMaxLogFileSize + ")");
154 return;
155 }
156 }
157 } catch (NullPointerException npe) {
158 Log.e(LOGTAG,"Exception while checking file size: " + npe);
159 }
160
161 FileOutputStream crashLogFile = null;
162 try {
163 crashLogFile = mAppContext.openFileOutput(CRASH_LOG_FILE, Context.MODE_APPEND);
164 crashLogFile.write(crashLog.getBytes());
165 } catch(IOException ioe) {
166 Log.e(LOGTAG,"Exception while writing file: " + ioe);
167 } finally {
168 if (crashLogFile != null) {
169 try {
170 crashLogFile.close();
171 } catch (IOException ignore) {
172 }
173 }
174 }
175 }
176
177 private void uploadPastCrashLog() {
178 FileInputStream crashLogFile = null;
179 BufferedReader reader = null;
180 try {
181 crashLogFile = mAppContext.openFileInput(CRASH_LOG_FILE);
182
183 reader = new BufferedReader(new InputStreamReader(crashLogFile));
184 StringBuilder crashLog = new StringBuilder();
185 String line = reader.readLine();
186 if (line != null) {
187 crashLog.append(line);
188 }
189
190 // Typically there's only one line (JSON string) in the crash
191 // log file. This loop would not be executed.
192 while ((line = reader.readLine()) != null) {
193 crashLog.append("\n").append(line);
194 }
195
Sagar Dhawanc0e1d432015-04-21 17:59:28 -0700196 uploadCrashLog(crashLog.toString(), UPLOAD_DELAY);
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700197 } catch(FileNotFoundException fnfe) {
198 Log.v(LOGTAG,"No previous crash found");
199 } catch(IOException ioe) {
200 Log.e(LOGTAG,"Exception while reading crash file: " + ioe);
201 } finally {
202 if (crashLogFile != null) {
203 try {
204 crashLogFile.close();
205 } catch (IOException ignore) {
206 }
207 }
208 if (reader != null) {
209 try {
210 reader.close();
211 } catch (IOException ignore) {
212 }
213 }
214 }
215 }
216
217 private void uploadCrashLog(String data, int after) {
218 final String crashLog = data;
219 final int waitFor = after;
220 new Thread(new Runnable() {
221 public void run(){
222 try {
223 SystemClock.sleep(waitFor);
224 AndroidHttpClient httpClient = AndroidHttpClient.newInstance("Android");;
225 HttpPost httpPost = new HttpPost(mLogServer);
226 HttpEntity se = new StringEntity(crashLog);
227 httpPost.setEntity(se);
228 HttpResponse response = httpClient.execute(httpPost);
229
230 File crashLogFile = new File(mAppContext.getFilesDir(),
231 CRASH_LOG_FILE);
232 if (crashLogFile != null) {
233 crashLogFile.delete();
234 } else {
235 Log.e(LOGTAG,"crash log file could not be opened for deletion");
236 }
237 } catch (ClientProtocolException pe) {
238 Log.e(LOGTAG,"Exception while sending http post: " + pe);
239 } catch (IOException ioe1) {
240 Log.e(LOGTAG,"Exception while sending http post: " + ioe1);
241 }
242 }
243 }).start();
244 }
245
246 public void uncaughtException(Thread t, Throwable e) {
247 if (!mOverrideHandler) {
248 mDefaultHandler.uncaughtException(t, e);
249 return;
250 }
251
252 String crashLog = new String();
253
254 try {
255 Calendar calendar = Calendar.getInstance();
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800256 JSONObject jsonBackTraceObj = new JSONObject();
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700257 String date = calendar.getTime().toString();
258 String aboutSWE = mAppContext.getResources().getString(R.string.about_text);
Pankaj Gargd106f372015-01-20 18:46:09 -0800259 String sweVer = findValueFromAboutText(aboutSWE, "Version: ");
260 String sweHash = findValueFromAboutText(aboutSWE, "Hash: ");
261 String sweBuildDate = findValueFromAboutText(aboutSWE, "Built: ");
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700262
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800263 jsonBackTraceObj.put("date", date);
Pankaj Gargd106f372015-01-20 18:46:09 -0800264 jsonBackTraceObj.put("android-model", android.os.Build.MODEL);
265 jsonBackTraceObj.put("android-device", android.os.Build.DEVICE);
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800266 jsonBackTraceObj.put("android-ver", android.os.Build.VERSION.RELEASE);
267 jsonBackTraceObj.put("browser-ver", sweVer);
268 jsonBackTraceObj.put("browser-hash", sweHash);
269 jsonBackTraceObj.put("browser-build-date", sweBuildDate);
270 jsonBackTraceObj.put("thread", t.toString());
Pankaj Gargd106f372015-01-20 18:46:09 -0800271 jsonBackTraceObj.put("format", "crashmon-1");
272 jsonBackTraceObj.put("monkey-test", ActivityManager.isUserAMonkey());
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700273
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800274 JSONArray jsonStackArray = new JSONArray();
275
276 Throwable throwable = e;
277 String stackTag = "Exception thrown while running";
278 while (throwable != null) {
279 JSONObject jsonStackObj = new JSONObject();
280 StackTraceElement[] arr = throwable.getStackTrace();
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700281 JSONArray jsonStack = new JSONArray(arr);
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800282
283 jsonStackObj.put("cause", throwable.getCause());
284 jsonStackObj.put("message", throwable.getMessage());
285 jsonStackObj.put(stackTag, jsonStack);
286
287 jsonStackArray.put(jsonStackObj);
288
289 stackTag = "stack";
290 throwable = throwable.getCause();
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700291 }
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800292 jsonBackTraceObj.put("exceptions", jsonStackArray);
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700293
294 JSONObject jsonMainObj = new JSONObject();
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800295 jsonMainObj.put("backtraces", jsonBackTraceObj);
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700296
297 Log.e(LOGTAG, "Exception: " + jsonMainObj.toString(4));
298 crashLog = jsonMainObj.toString();
299
300 } catch (JSONException je) {
301 Log.e(LOGTAG, "Failed in JSON encoding: " + je);
302 }
303
304 saveCrashLog(crashLog);
305
306 uploadCrashLog(crashLog, 0);
307
308 mDefaultHandler.uncaughtException(t, e);
309 }
Pankaj Garg5fb557e2014-12-22 14:59:18 -0800310
311 private String findValueFromAboutText(String aboutText, String aboutKey) {
312 int start = aboutText.indexOf(aboutKey);
313 int end = aboutText.indexOf("\n", start);
314 String value = "";
315
316 if (start != -1 && end != -1) {
317 start += aboutKey.length();
318 value = aboutText.substring(start, end);
319 }
320 return value;
321 }
322
Sagar Dhawanc0e1d432015-04-21 17:59:28 -0700323 private void checkNativeCrash(final File crashReportsDir) {
324 // Search cache/Crash Reports/ for any crashes
325 if (crashReportsDir.exists()) {
326 new Thread(new Runnable() {
327 @Override
328 public void run() {
329 for (File f : crashReportsDir.listFiles()) {
330 uploadNativeCrashReport(f);
331 }
332 }
333 }).start();
334 }
335 }
336
337 private void uploadNativeCrashReport(final File report) {
338 Log.w(LOGTAG, "Preparing Crash Report for upload " + report.getName());
339 // get server url from commandline
Tarun Nainanif03c7962015-06-03 11:38:33 -0700340 String server = BrowserCommandLine.getSwitchValue(BrowserSwitches.CRASH_LOG_SERVER_CMD);
Sagar Dhawanc0e1d432015-04-21 17:59:28 -0700341 try {
342 HttpClient httpClient = new DefaultHttpClient();
343 HttpPost httpPost = new HttpPost(server);
344 InputStreamEntity isEntity = new InputStreamEntity(
345 new FileInputStream(report), -1);
346 // Send the report as a Binary
347 isEntity.setContentType("binary/octet-stream");
348 isEntity.setChunked(false);
349 httpPost.setEntity(isEntity);
350 HttpResponse response = httpClient.execute(httpPost);
351 int status = response.getStatusLine().getStatusCode();
352 if (status == 200)
353 report.delete();
354 else Log.w(LOGTAG, "Upload Failure. Will try again next time- " + status);
355
356 } catch (Exception e) {
357 Log.w(LOGTAG, "Crash Report failed to upload, will try again next time " + e);
358 }
359 }
360
Pankaj Gargf04dbda2014-10-02 13:52:46 -0700361}