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