mobile/android/base/ANRReporter.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 package org.mozilla.gecko;
michael@0 7
michael@0 8 import java.io.BufferedOutputStream;
michael@0 9 import java.io.BufferedReader;
michael@0 10 import java.io.File;
michael@0 11 import java.io.FileInputStream;
michael@0 12 import java.io.FileOutputStream;
michael@0 13 import java.io.FileReader;
michael@0 14 import java.io.IOException;
michael@0 15 import java.io.InputStreamReader;
michael@0 16 import java.io.OutputStream;
michael@0 17 import java.io.Reader;
michael@0 18 import java.util.UUID;
michael@0 19 import java.util.regex.Pattern;
michael@0 20
michael@0 21 import org.json.JSONObject;
michael@0 22 import org.mozilla.gecko.util.ThreadUtils;
michael@0 23
michael@0 24 import android.content.BroadcastReceiver;
michael@0 25 import android.content.Context;
michael@0 26 import android.content.Intent;
michael@0 27 import android.content.IntentFilter;
michael@0 28 import android.os.Handler;
michael@0 29 import android.os.Looper;
michael@0 30 import android.util.Log;
michael@0 31
michael@0 32 public final class ANRReporter extends BroadcastReceiver
michael@0 33 {
michael@0 34 private static final boolean DEBUG = false;
michael@0 35 private static final String LOGTAG = "GeckoANRReporter";
michael@0 36
michael@0 37 private static final String ANR_ACTION = "android.intent.action.ANR";
michael@0 38 // Number of lines to search traces.txt to decide whether it's a Gecko ANR
michael@0 39 private static final int LINES_TO_IDENTIFY_TRACES = 10;
michael@0 40 // ANRs may happen because of memory pressure,
michael@0 41 // so don't use up too much memory here
michael@0 42 // Size of buffer to hold one line of text
michael@0 43 private static final int TRACES_LINE_SIZE = 100;
michael@0 44 // Size of block to use when processing traces.txt
michael@0 45 private static final int TRACES_BLOCK_SIZE = 2000;
michael@0 46 private static final String TRACES_CHARSET = "utf-8";
michael@0 47 private static final String PING_CHARSET = "utf-8";
michael@0 48
michael@0 49 private static final ANRReporter sInstance = new ANRReporter();
michael@0 50 private static int sRegisteredCount;
michael@0 51 private Handler mHandler;
michael@0 52 private volatile boolean mPendingANR;
michael@0 53
michael@0 54 private static native boolean requestNativeStack(boolean unwind);
michael@0 55 private static native String getNativeStack();
michael@0 56 private static native void releaseNativeStack();
michael@0 57
michael@0 58 public static void register(Context context) {
michael@0 59 if (sRegisteredCount++ != 0) {
michael@0 60 // Already registered
michael@0 61 return;
michael@0 62 }
michael@0 63 sInstance.start(context);
michael@0 64 }
michael@0 65
michael@0 66 public static void unregister() {
michael@0 67 if (sRegisteredCount == 0) {
michael@0 68 Log.w(LOGTAG, "register/unregister mismatch");
michael@0 69 return;
michael@0 70 }
michael@0 71 if (--sRegisteredCount != 0) {
michael@0 72 // Should still be registered
michael@0 73 return;
michael@0 74 }
michael@0 75 sInstance.stop();
michael@0 76 }
michael@0 77
michael@0 78 private void start(final Context context) {
michael@0 79
michael@0 80 Thread receiverThread = new Thread(new Runnable() {
michael@0 81 @Override
michael@0 82 public void run() {
michael@0 83 Looper.prepare();
michael@0 84 synchronized (ANRReporter.this) {
michael@0 85 mHandler = new Handler();
michael@0 86 ANRReporter.this.notify();
michael@0 87 }
michael@0 88 if (DEBUG) {
michael@0 89 Log.d(LOGTAG, "registering receiver");
michael@0 90 }
michael@0 91 context.registerReceiver(ANRReporter.this,
michael@0 92 new IntentFilter(ANR_ACTION),
michael@0 93 null,
michael@0 94 mHandler);
michael@0 95 Looper.loop();
michael@0 96
michael@0 97 if (DEBUG) {
michael@0 98 Log.d(LOGTAG, "unregistering receiver");
michael@0 99 }
michael@0 100 context.unregisterReceiver(ANRReporter.this);
michael@0 101 mHandler = null;
michael@0 102 }
michael@0 103 }, LOGTAG);
michael@0 104
michael@0 105 receiverThread.setDaemon(true);
michael@0 106 receiverThread.start();
michael@0 107 }
michael@0 108
michael@0 109 private void stop() {
michael@0 110 synchronized (this) {
michael@0 111 while (mHandler == null) {
michael@0 112 try {
michael@0 113 wait(1000);
michael@0 114 if (mHandler == null) {
michael@0 115 // We timed out; just give up. The process is probably
michael@0 116 // quitting anyways, so we let the OS do the clean up
michael@0 117 Log.w(LOGTAG, "timed out waiting for handler");
michael@0 118 return;
michael@0 119 }
michael@0 120 } catch (InterruptedException e) {
michael@0 121 }
michael@0 122 }
michael@0 123 }
michael@0 124 Looper looper = mHandler.getLooper();
michael@0 125 looper.quit();
michael@0 126 try {
michael@0 127 looper.getThread().join();
michael@0 128 } catch (InterruptedException e) {
michael@0 129 }
michael@0 130 }
michael@0 131
michael@0 132 private ANRReporter() {
michael@0 133 }
michael@0 134
michael@0 135 // Return the "traces.txt" file, or null if there is no such file
michael@0 136 private static File getTracesFile() {
michael@0 137 try {
michael@0 138 // getprop [prop-name [default-value]]
michael@0 139 Process propProc = (new ProcessBuilder())
michael@0 140 .command("/system/bin/getprop", "dalvik.vm.stack-trace-file")
michael@0 141 .redirectErrorStream(true)
michael@0 142 .start();
michael@0 143 try {
michael@0 144 BufferedReader buf = new BufferedReader(
michael@0 145 new InputStreamReader(propProc.getInputStream()), TRACES_LINE_SIZE);
michael@0 146 String propVal = buf.readLine();
michael@0 147 if (DEBUG) {
michael@0 148 Log.d(LOGTAG, "getprop returned " + String.valueOf(propVal));
michael@0 149 }
michael@0 150 // getprop can return empty string when the prop value is empty
michael@0 151 // or prop is undefined, treat both cases the same way
michael@0 152 if (propVal != null && propVal.length() != 0) {
michael@0 153 File tracesFile = new File(propVal);
michael@0 154 if (tracesFile.isFile() && tracesFile.canRead()) {
michael@0 155 return tracesFile;
michael@0 156 } else if (DEBUG) {
michael@0 157 Log.d(LOGTAG, "cannot access traces file");
michael@0 158 }
michael@0 159 } else if (DEBUG) {
michael@0 160 Log.d(LOGTAG, "empty getprop result");
michael@0 161 }
michael@0 162 } finally {
michael@0 163 propProc.destroy();
michael@0 164 }
michael@0 165 } catch (IOException e) {
michael@0 166 Log.w(LOGTAG, e);
michael@0 167 } catch (ClassCastException e) {
michael@0 168 Log.w(LOGTAG, e); // Bug 975436
michael@0 169 }
michael@0 170 // Check most common location one last time just in case
michael@0 171 File tracesFile = new File("/data/anr/traces.txt");
michael@0 172 if (tracesFile.isFile() && tracesFile.canRead()) {
michael@0 173 return tracesFile;
michael@0 174 }
michael@0 175 return null;
michael@0 176 }
michael@0 177
michael@0 178 private static File getPingFile() {
michael@0 179 if (GeckoAppShell.getContext() == null) {
michael@0 180 return null;
michael@0 181 }
michael@0 182 GeckoProfile profile = GeckoAppShell.getGeckoInterface().getProfile();
michael@0 183 if (profile == null) {
michael@0 184 return null;
michael@0 185 }
michael@0 186 File profDir = profile.getDir();
michael@0 187 if (profDir == null) {
michael@0 188 return null;
michael@0 189 }
michael@0 190 File pingDir = new File(profDir, "saved-telemetry-pings");
michael@0 191 pingDir.mkdirs();
michael@0 192 if (!(pingDir.exists() && pingDir.isDirectory())) {
michael@0 193 return null;
michael@0 194 }
michael@0 195 return new File(pingDir, UUID.randomUUID().toString());
michael@0 196 }
michael@0 197
michael@0 198 // Return true if the traces file corresponds to a Gecko ANR
michael@0 199 private static boolean isGeckoTraces(String pkgName, File tracesFile) {
michael@0 200 try {
michael@0 201 final String END_OF_PACKAGE_NAME = "([^a-zA-Z0-9_]|$)";
michael@0 202 // Regex for finding our package name in the traces file
michael@0 203 Pattern pkgPattern = Pattern.compile(Pattern.quote(pkgName) + END_OF_PACKAGE_NAME);
michael@0 204 Pattern mangledPattern = null;
michael@0 205 if (!AppConstants.MANGLED_ANDROID_PACKAGE_NAME.equals(pkgName)) {
michael@0 206 mangledPattern = Pattern.compile(Pattern.quote(
michael@0 207 AppConstants.MANGLED_ANDROID_PACKAGE_NAME) + END_OF_PACKAGE_NAME);
michael@0 208 }
michael@0 209 if (DEBUG) {
michael@0 210 Log.d(LOGTAG, "trying to match package: " + pkgName);
michael@0 211 }
michael@0 212 BufferedReader traces = new BufferedReader(
michael@0 213 new FileReader(tracesFile), TRACES_BLOCK_SIZE);
michael@0 214 try {
michael@0 215 for (int count = 0; count < LINES_TO_IDENTIFY_TRACES; count++) {
michael@0 216 String line = traces.readLine();
michael@0 217 if (DEBUG) {
michael@0 218 Log.d(LOGTAG, "identifying line: " + String.valueOf(line));
michael@0 219 }
michael@0 220 if (line == null) {
michael@0 221 if (DEBUG) {
michael@0 222 Log.d(LOGTAG, "reached end of traces file");
michael@0 223 }
michael@0 224 return false;
michael@0 225 }
michael@0 226 if (pkgPattern.matcher(line).find()) {
michael@0 227 // traces.txt file contains our package
michael@0 228 return true;
michael@0 229 }
michael@0 230 if (mangledPattern != null && mangledPattern.matcher(line).find()) {
michael@0 231 // traces.txt file contains our alternate package
michael@0 232 return true;
michael@0 233 }
michael@0 234 }
michael@0 235 } finally {
michael@0 236 traces.close();
michael@0 237 }
michael@0 238 } catch (IOException e) {
michael@0 239 // meh, can't even read from it right. just return false
michael@0 240 }
michael@0 241 return false;
michael@0 242 }
michael@0 243
michael@0 244 private static long getUptimeMins() {
michael@0 245
michael@0 246 long uptimeMins = (new File("/proc/self/stat")).lastModified();
michael@0 247 if (uptimeMins != 0L) {
michael@0 248 uptimeMins = (System.currentTimeMillis() - uptimeMins) / 1000L / 60L;
michael@0 249 if (DEBUG) {
michael@0 250 Log.d(LOGTAG, "uptime " + String.valueOf(uptimeMins));
michael@0 251 }
michael@0 252 return uptimeMins;
michael@0 253 } else if (DEBUG) {
michael@0 254 Log.d(LOGTAG, "could not get uptime");
michael@0 255 }
michael@0 256 return 0L;
michael@0 257 }
michael@0 258
michael@0 259 /*
michael@0 260 a saved telemetry ping file consists of JSON in the following format,
michael@0 261 {
michael@0 262 "reason": "android-anr-report",
michael@0 263 "slug": "<uuid-string>",
michael@0 264 "payload": <json-object>
michael@0 265 }
michael@0 266 for Android ANR, our JSON payload should look like,
michael@0 267 {
michael@0 268 "ver": 1,
michael@0 269 "simpleMeasurements": {
michael@0 270 "uptime": <uptime>
michael@0 271 },
michael@0 272 "info": {
michael@0 273 "reason": "android-anr-report",
michael@0 274 "OS": "Android",
michael@0 275 ...
michael@0 276 },
michael@0 277 "androidANR": "...",
michael@0 278 "androidLogcat": "..."
michael@0 279 }
michael@0 280 */
michael@0 281
michael@0 282 private static int writePingPayload(OutputStream ping,
michael@0 283 String payload) throws IOException {
michael@0 284 byte [] data = payload.getBytes(PING_CHARSET);
michael@0 285 ping.write(data);
michael@0 286 return data.length;
michael@0 287 }
michael@0 288
michael@0 289 private static void fillPingHeader(OutputStream ping, String slug)
michael@0 290 throws IOException {
michael@0 291
michael@0 292 // ping file header
michael@0 293 byte [] data = ("{" +
michael@0 294 "\"reason\":\"android-anr-report\"," +
michael@0 295 "\"slug\":" + JSONObject.quote(slug) + "," +
michael@0 296 "\"payload\":").getBytes(PING_CHARSET);
michael@0 297 ping.write(data);
michael@0 298 if (DEBUG) {
michael@0 299 Log.d(LOGTAG, "wrote ping header, size = " + String.valueOf(data.length));
michael@0 300 }
michael@0 301
michael@0 302 // payload start
michael@0 303 int size = writePingPayload(ping, ("{" +
michael@0 304 "\"ver\":1," +
michael@0 305 "\"simpleMeasurements\":{" +
michael@0 306 "\"uptime\":" + String.valueOf(getUptimeMins()) +
michael@0 307 "}," +
michael@0 308 "\"info\":{" +
michael@0 309 "\"reason\":\"android-anr-report\"," +
michael@0 310 "\"OS\":" + JSONObject.quote(SysInfo.getName()) + "," +
michael@0 311 "\"version\":\"" + String.valueOf(SysInfo.getVersion()) + "\"," +
michael@0 312 "\"appID\":" + JSONObject.quote(AppConstants.MOZ_APP_ID) + "," +
michael@0 313 "\"appVersion\":" + JSONObject.quote(AppConstants.MOZ_APP_VERSION)+ "," +
michael@0 314 "\"appName\":" + JSONObject.quote(AppConstants.MOZ_APP_BASENAME) + "," +
michael@0 315 "\"appBuildID\":" + JSONObject.quote(AppConstants.MOZ_APP_BUILDID) + "," +
michael@0 316 "\"appUpdateChannel\":" + JSONObject.quote(AppConstants.MOZ_UPDATE_CHANNEL) + "," +
michael@0 317 // Technically the platform build ID may be different, but we'll never know
michael@0 318 "\"platformBuildID\":" + JSONObject.quote(AppConstants.MOZ_APP_BUILDID) + "," +
michael@0 319 "\"locale\":" + JSONObject.quote(SysInfo.getLocale()) + "," +
michael@0 320 "\"cpucount\":" + String.valueOf(SysInfo.getCPUCount()) + "," +
michael@0 321 "\"memsize\":" + String.valueOf(SysInfo.getMemSize()) + "," +
michael@0 322 "\"arch\":" + JSONObject.quote(SysInfo.getArchABI()) + "," +
michael@0 323 "\"kernel_version\":" + JSONObject.quote(SysInfo.getKernelVersion()) + "," +
michael@0 324 "\"device\":" + JSONObject.quote(SysInfo.getDevice()) + "," +
michael@0 325 "\"manufacturer\":" + JSONObject.quote(SysInfo.getManufacturer()) + "," +
michael@0 326 "\"hardware\":" + JSONObject.quote(SysInfo.getHardware()) +
michael@0 327 "}," +
michael@0 328 "\"androidANR\":\""));
michael@0 329 if (DEBUG) {
michael@0 330 Log.d(LOGTAG, "wrote metadata, size = " + String.valueOf(size));
michael@0 331 }
michael@0 332
michael@0 333 // We are at the start of ANR data
michael@0 334 }
michael@0 335
michael@0 336 // Block is a section of the larger input stream, and we want to find pattern within
michael@0 337 // the stream. This is straightforward if the entire pattern is within one block;
michael@0 338 // however, if the pattern spans across two blocks, we have to match both the start of
michael@0 339 // the pattern in the first block and the end of the pattern in the second block.
michael@0 340 // * If pattern is found in block, this method returns the index at the end of the
michael@0 341 // found pattern, which must always be > 0.
michael@0 342 // * If pattern is not found, it returns 0.
michael@0 343 // * If the start of the pattern matches the end of the block, it returns a number
michael@0 344 // < 0, which equals the negated value of how many characters in pattern are already
michael@0 345 // matched; when processing the next block, this number is passed in through
michael@0 346 // prevIndex, and the rest of the characters in pattern are matched against the
michael@0 347 // start of this second block. The method returns value > 0 if the rest of the
michael@0 348 // characters match, or 0 if they do not.
michael@0 349 private static int getEndPatternIndex(String block, String pattern, int prevIndex) {
michael@0 350 if (pattern == null || block.length() < pattern.length()) {
michael@0 351 // Nothing to do
michael@0 352 return 0;
michael@0 353 }
michael@0 354 if (prevIndex < 0) {
michael@0 355 // Last block ended with a partial start; now match start of block to rest of pattern
michael@0 356 if (block.startsWith(pattern.substring(-prevIndex, pattern.length()))) {
michael@0 357 // Rest of pattern matches; return index at end of pattern
michael@0 358 return pattern.length() + prevIndex;
michael@0 359 }
michael@0 360 // Not a match; continue with normal search
michael@0 361 }
michael@0 362 // Did not find pattern in last block; see if entire pattern is inside this block
michael@0 363 int index = block.indexOf(pattern);
michael@0 364 if (index >= 0) {
michael@0 365 // Found pattern; return index at end of the pattern
michael@0 366 return index + pattern.length();
michael@0 367 }
michael@0 368 // Block does not contain the entire pattern, but see if the end of the block
michael@0 369 // contains the start of pattern. To do that, we see if block ends with the
michael@0 370 // first n-1 characters of pattern, the first n-2 characters of pattern, etc.
michael@0 371 for (index = block.length() - pattern.length() + 1; index < block.length(); index++) {
michael@0 372 // Using index as a start, see if the rest of block contains the start of pattern
michael@0 373 if (block.charAt(index) == pattern.charAt(0) &&
michael@0 374 block.endsWith(pattern.substring(0, block.length() - index))) {
michael@0 375 // Found partial match; return -(number of characters matched),
michael@0 376 // i.e. -1 for 1 character matched, -2 for 2 characters matched, etc.
michael@0 377 return index - block.length();
michael@0 378 }
michael@0 379 }
michael@0 380 return 0;
michael@0 381 }
michael@0 382
michael@0 383 // Copy the content of reader to ping;
michael@0 384 // copying stops when endPattern is found in the input stream
michael@0 385 private static int fillPingBlock(OutputStream ping,
michael@0 386 Reader reader, String endPattern)
michael@0 387 throws IOException {
michael@0 388
michael@0 389 int total = 0;
michael@0 390 int endIndex = 0;
michael@0 391 char [] block = new char[TRACES_BLOCK_SIZE];
michael@0 392 for (int size = reader.read(block); size >= 0; size = reader.read(block)) {
michael@0 393 String stringBlock = new String(block, 0, size);
michael@0 394 endIndex = getEndPatternIndex(stringBlock, endPattern, endIndex);
michael@0 395 if (endIndex > 0) {
michael@0 396 // Found end pattern; clip the string
michael@0 397 stringBlock = stringBlock.substring(0, endIndex);
michael@0 398 }
michael@0 399 String quoted = JSONObject.quote(stringBlock);
michael@0 400 total += writePingPayload(ping, quoted.substring(1, quoted.length() - 1));
michael@0 401 if (endIndex > 0) {
michael@0 402 // End pattern already found; return now
michael@0 403 break;
michael@0 404 }
michael@0 405 }
michael@0 406 return total;
michael@0 407 }
michael@0 408
michael@0 409 private static void fillPingFooter(OutputStream ping,
michael@0 410 boolean haveNativeStack)
michael@0 411 throws IOException {
michael@0 412
michael@0 413 // We are at the end of ANR data
michael@0 414
michael@0 415 int total = writePingPayload(ping, ("\"," +
michael@0 416 "\"androidLogcat\":\""));
michael@0 417
michael@0 418 try {
michael@0 419 // get the last 200 lines of logcat
michael@0 420 Process proc = (new ProcessBuilder())
michael@0 421 .command("/system/bin/logcat", "-v", "threadtime", "-t", "200", "-d", "*:D")
michael@0 422 .redirectErrorStream(true)
michael@0 423 .start();
michael@0 424 try {
michael@0 425 Reader procOut = new InputStreamReader(proc.getInputStream(), TRACES_CHARSET);
michael@0 426 int size = fillPingBlock(ping, procOut, null);
michael@0 427 if (DEBUG) {
michael@0 428 Log.d(LOGTAG, "wrote logcat, size = " + String.valueOf(size));
michael@0 429 }
michael@0 430 } finally {
michael@0 431 proc.destroy();
michael@0 432 }
michael@0 433 } catch (IOException e) {
michael@0 434 // ignore because logcat is not essential
michael@0 435 Log.w(LOGTAG, e);
michael@0 436 }
michael@0 437
michael@0 438 if (haveNativeStack) {
michael@0 439 total += writePingPayload(ping, ("\"," +
michael@0 440 "\"androidNativeStack\":"));
michael@0 441
michael@0 442 String nativeStack = String.valueOf(getNativeStack());
michael@0 443 int size = writePingPayload(ping, nativeStack);
michael@0 444 if (DEBUG) {
michael@0 445 Log.d(LOGTAG, "wrote native stack, size = " + String.valueOf(size));
michael@0 446 }
michael@0 447 total += size + writePingPayload(ping, "}");
michael@0 448 } else {
michael@0 449 total += writePingPayload(ping, "\"}");
michael@0 450 }
michael@0 451
michael@0 452 byte [] data = (
michael@0 453 "}").getBytes(PING_CHARSET);
michael@0 454 ping.write(data);
michael@0 455 if (DEBUG) {
michael@0 456 Log.d(LOGTAG, "wrote ping footer, size = " + String.valueOf(data.length + total));
michael@0 457 }
michael@0 458 }
michael@0 459
michael@0 460 private static void processTraces(Reader traces, File pingFile) {
michael@0 461
michael@0 462 // Unwinding is memory intensive; only unwind if we have enough memory
michael@0 463 boolean haveNativeStack = requestNativeStack(
michael@0 464 /* unwind */ SysInfo.getMemSize() >= 640);
michael@0 465 try {
michael@0 466 OutputStream ping = new BufferedOutputStream(
michael@0 467 new FileOutputStream(pingFile), TRACES_BLOCK_SIZE);
michael@0 468 try {
michael@0 469 fillPingHeader(ping, pingFile.getName());
michael@0 470 // Traces file has the format
michael@0 471 // ----- pid xxx at xxx -----
michael@0 472 // Cmd line: org.mozilla.xxx
michael@0 473 // * stack trace *
michael@0 474 // ----- end xxx -----
michael@0 475 // ----- pid xxx at xxx -----
michael@0 476 // Cmd line: com.android.xxx
michael@0 477 // * stack trace *
michael@0 478 // ...
michael@0 479 // If we end the stack dump at the first end marker,
michael@0 480 // only Fennec stacks will be dumped
michael@0 481 int size = fillPingBlock(ping, traces, "\n----- end");
michael@0 482 if (DEBUG) {
michael@0 483 Log.d(LOGTAG, "wrote traces, size = " + String.valueOf(size));
michael@0 484 }
michael@0 485 fillPingFooter(ping, haveNativeStack);
michael@0 486 if (DEBUG) {
michael@0 487 Log.d(LOGTAG, "finished creating ping file");
michael@0 488 }
michael@0 489 return;
michael@0 490 } finally {
michael@0 491 ping.close();
michael@0 492 if (haveNativeStack) {
michael@0 493 releaseNativeStack();
michael@0 494 }
michael@0 495 }
michael@0 496 } catch (IOException e) {
michael@0 497 Log.w(LOGTAG, e);
michael@0 498 }
michael@0 499 // exception; delete ping file
michael@0 500 if (pingFile.exists()) {
michael@0 501 pingFile.delete();
michael@0 502 }
michael@0 503 }
michael@0 504
michael@0 505 private static void processTraces(File tracesFile, File pingFile) {
michael@0 506 try {
michael@0 507 Reader traces = new InputStreamReader(
michael@0 508 new FileInputStream(tracesFile), TRACES_CHARSET);
michael@0 509 try {
michael@0 510 processTraces(traces, pingFile);
michael@0 511 } finally {
michael@0 512 traces.close();
michael@0 513 }
michael@0 514 } catch (IOException e) {
michael@0 515 Log.w(LOGTAG, e);
michael@0 516 }
michael@0 517 }
michael@0 518
michael@0 519 @Override
michael@0 520 public void onReceive(Context context, Intent intent) {
michael@0 521 if (mPendingANR) {
michael@0 522 // we already processed an ANR without getting unstuck; skip this one
michael@0 523 if (DEBUG) {
michael@0 524 Log.d(LOGTAG, "skipping duplicate ANR");
michael@0 525 }
michael@0 526 return;
michael@0 527 }
michael@0 528 if (ThreadUtils.getUiHandler() != null) {
michael@0 529 mPendingANR = true;
michael@0 530 // detect when the main thread gets unstuck
michael@0 531 ThreadUtils.postToUiThread(new Runnable() {
michael@0 532 @Override
michael@0 533 public void run() {
michael@0 534 // okay to reset mPendingANR on main thread
michael@0 535 mPendingANR = false;
michael@0 536 if (DEBUG) {
michael@0 537 Log.d(LOGTAG, "yay we got unstuck!");
michael@0 538 }
michael@0 539 }
michael@0 540 });
michael@0 541 }
michael@0 542 if (DEBUG) {
michael@0 543 Log.d(LOGTAG, "receiving " + String.valueOf(intent));
michael@0 544 }
michael@0 545 if (!ANR_ACTION.equals(intent.getAction())) {
michael@0 546 return;
michael@0 547 }
michael@0 548
michael@0 549 // make sure we have a good save location first
michael@0 550 File pingFile = getPingFile();
michael@0 551 if (DEBUG) {
michael@0 552 Log.d(LOGTAG, "using ping file: " + String.valueOf(pingFile));
michael@0 553 }
michael@0 554 if (pingFile == null) {
michael@0 555 return;
michael@0 556 }
michael@0 557
michael@0 558 File tracesFile = getTracesFile();
michael@0 559 if (DEBUG) {
michael@0 560 Log.d(LOGTAG, "using traces file: " + String.valueOf(tracesFile));
michael@0 561 }
michael@0 562 if (tracesFile == null) {
michael@0 563 return;
michael@0 564 }
michael@0 565
michael@0 566 // We get ANR intents from all ANRs in the system, but we only want Gecko ANRs
michael@0 567 if (!isGeckoTraces(context.getPackageName(), tracesFile)) {
michael@0 568 if (DEBUG) {
michael@0 569 Log.d(LOGTAG, "traces is not Gecko ANR");
michael@0 570 }
michael@0 571 return;
michael@0 572 }
michael@0 573 Log.i(LOGTAG, "processing Gecko ANR");
michael@0 574 processTraces(tracesFile, pingFile);
michael@0 575 }
michael@0 576 }

mercurial