mobile/android/base/ANRReporter.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/ANRReporter.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,576 @@
     1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +package org.mozilla.gecko;
    1.10 +
    1.11 +import java.io.BufferedOutputStream;
    1.12 +import java.io.BufferedReader;
    1.13 +import java.io.File;
    1.14 +import java.io.FileInputStream;
    1.15 +import java.io.FileOutputStream;
    1.16 +import java.io.FileReader;
    1.17 +import java.io.IOException;
    1.18 +import java.io.InputStreamReader;
    1.19 +import java.io.OutputStream;
    1.20 +import java.io.Reader;
    1.21 +import java.util.UUID;
    1.22 +import java.util.regex.Pattern;
    1.23 +
    1.24 +import org.json.JSONObject;
    1.25 +import org.mozilla.gecko.util.ThreadUtils;
    1.26 +
    1.27 +import android.content.BroadcastReceiver;
    1.28 +import android.content.Context;
    1.29 +import android.content.Intent;
    1.30 +import android.content.IntentFilter;
    1.31 +import android.os.Handler;
    1.32 +import android.os.Looper;
    1.33 +import android.util.Log;
    1.34 +
    1.35 +public final class ANRReporter extends BroadcastReceiver
    1.36 +{
    1.37 +    private static final boolean DEBUG = false;
    1.38 +    private static final String LOGTAG = "GeckoANRReporter";
    1.39 +
    1.40 +    private static final String ANR_ACTION = "android.intent.action.ANR";
    1.41 +    // Number of lines to search traces.txt to decide whether it's a Gecko ANR
    1.42 +    private static final int LINES_TO_IDENTIFY_TRACES = 10;
    1.43 +    // ANRs may happen because of memory pressure,
    1.44 +    //  so don't use up too much memory here
    1.45 +    // Size of buffer to hold one line of text
    1.46 +    private static final int TRACES_LINE_SIZE = 100;
    1.47 +    // Size of block to use when processing traces.txt
    1.48 +    private static final int TRACES_BLOCK_SIZE = 2000;
    1.49 +    private static final String TRACES_CHARSET = "utf-8";
    1.50 +    private static final String PING_CHARSET = "utf-8";
    1.51 +
    1.52 +    private static final ANRReporter sInstance = new ANRReporter();
    1.53 +    private static int sRegisteredCount;
    1.54 +    private Handler mHandler;
    1.55 +    private volatile boolean mPendingANR;
    1.56 +
    1.57 +    private static native boolean requestNativeStack(boolean unwind);
    1.58 +    private static native String getNativeStack();
    1.59 +    private static native void releaseNativeStack();
    1.60 +
    1.61 +    public static void register(Context context) {
    1.62 +        if (sRegisteredCount++ != 0) {
    1.63 +            // Already registered
    1.64 +            return;
    1.65 +        }
    1.66 +        sInstance.start(context);
    1.67 +    }
    1.68 +
    1.69 +    public static void unregister() {
    1.70 +        if (sRegisteredCount == 0) {
    1.71 +            Log.w(LOGTAG, "register/unregister mismatch");
    1.72 +            return;
    1.73 +        }
    1.74 +        if (--sRegisteredCount != 0) {
    1.75 +            // Should still be registered
    1.76 +            return;
    1.77 +        }
    1.78 +        sInstance.stop();
    1.79 +    }
    1.80 +
    1.81 +    private void start(final Context context) {
    1.82 +
    1.83 +        Thread receiverThread = new Thread(new Runnable() {
    1.84 +            @Override
    1.85 +            public void run() {
    1.86 +                Looper.prepare();
    1.87 +                synchronized (ANRReporter.this) {
    1.88 +                    mHandler = new Handler();
    1.89 +                    ANRReporter.this.notify();
    1.90 +                }
    1.91 +                if (DEBUG) {
    1.92 +                    Log.d(LOGTAG, "registering receiver");
    1.93 +                }
    1.94 +                context.registerReceiver(ANRReporter.this,
    1.95 +                                         new IntentFilter(ANR_ACTION),
    1.96 +                                         null,
    1.97 +                                         mHandler);
    1.98 +                Looper.loop();
    1.99 +
   1.100 +                if (DEBUG) {
   1.101 +                    Log.d(LOGTAG, "unregistering receiver");
   1.102 +                }
   1.103 +                context.unregisterReceiver(ANRReporter.this);
   1.104 +                mHandler = null;
   1.105 +            }
   1.106 +        }, LOGTAG);
   1.107 +
   1.108 +        receiverThread.setDaemon(true);
   1.109 +        receiverThread.start();
   1.110 +    }
   1.111 +
   1.112 +    private void stop() {
   1.113 +        synchronized (this) {
   1.114 +            while (mHandler == null) {
   1.115 +                try {
   1.116 +                    wait(1000);
   1.117 +                    if (mHandler == null) {
   1.118 +                        // We timed out; just give up. The process is probably
   1.119 +                        // quitting anyways, so we let the OS do the clean up
   1.120 +                        Log.w(LOGTAG, "timed out waiting for handler");
   1.121 +                        return;
   1.122 +                    }
   1.123 +                } catch (InterruptedException e) {
   1.124 +                }
   1.125 +            }
   1.126 +        }
   1.127 +        Looper looper = mHandler.getLooper();
   1.128 +        looper.quit();
   1.129 +        try {
   1.130 +            looper.getThread().join();
   1.131 +        } catch (InterruptedException e) {
   1.132 +        }
   1.133 +    }
   1.134 +
   1.135 +    private ANRReporter() {
   1.136 +    }
   1.137 +
   1.138 +    // Return the "traces.txt" file, or null if there is no such file
   1.139 +    private static File getTracesFile() {
   1.140 +        try {
   1.141 +            // getprop [prop-name [default-value]]
   1.142 +            Process propProc = (new ProcessBuilder())
   1.143 +                .command("/system/bin/getprop", "dalvik.vm.stack-trace-file")
   1.144 +                .redirectErrorStream(true)
   1.145 +                .start();
   1.146 +            try {
   1.147 +                BufferedReader buf = new BufferedReader(
   1.148 +                    new InputStreamReader(propProc.getInputStream()), TRACES_LINE_SIZE);
   1.149 +                String propVal = buf.readLine();
   1.150 +                if (DEBUG) {
   1.151 +                    Log.d(LOGTAG, "getprop returned " + String.valueOf(propVal));
   1.152 +                }
   1.153 +                // getprop can return empty string when the prop value is empty
   1.154 +                // or prop is undefined, treat both cases the same way
   1.155 +                if (propVal != null && propVal.length() != 0) {
   1.156 +                    File tracesFile = new File(propVal);
   1.157 +                    if (tracesFile.isFile() && tracesFile.canRead()) {
   1.158 +                        return tracesFile;
   1.159 +                    } else if (DEBUG) {
   1.160 +                        Log.d(LOGTAG, "cannot access traces file");
   1.161 +                    }
   1.162 +                } else if (DEBUG) {
   1.163 +                    Log.d(LOGTAG, "empty getprop result");
   1.164 +                }
   1.165 +            } finally {
   1.166 +                propProc.destroy();
   1.167 +            }
   1.168 +        } catch (IOException e) {
   1.169 +            Log.w(LOGTAG, e);
   1.170 +        } catch (ClassCastException e) {
   1.171 +            Log.w(LOGTAG, e); // Bug 975436
   1.172 +        }
   1.173 +        // Check most common location one last time just in case
   1.174 +        File tracesFile = new File("/data/anr/traces.txt");
   1.175 +        if (tracesFile.isFile() && tracesFile.canRead()) {
   1.176 +            return tracesFile;
   1.177 +        }
   1.178 +        return null;
   1.179 +    }
   1.180 +
   1.181 +    private static File getPingFile() {
   1.182 +        if (GeckoAppShell.getContext() == null) {
   1.183 +            return null;
   1.184 +        }
   1.185 +        GeckoProfile profile = GeckoAppShell.getGeckoInterface().getProfile();
   1.186 +        if (profile == null) {
   1.187 +            return null;
   1.188 +        }
   1.189 +        File profDir = profile.getDir();
   1.190 +        if (profDir == null) {
   1.191 +            return null;
   1.192 +        }
   1.193 +        File pingDir = new File(profDir, "saved-telemetry-pings");
   1.194 +        pingDir.mkdirs();
   1.195 +        if (!(pingDir.exists() && pingDir.isDirectory())) {
   1.196 +            return null;
   1.197 +        }
   1.198 +        return new File(pingDir, UUID.randomUUID().toString());
   1.199 +    }
   1.200 +
   1.201 +    // Return true if the traces file corresponds to a Gecko ANR
   1.202 +    private static boolean isGeckoTraces(String pkgName, File tracesFile) {
   1.203 +        try {
   1.204 +            final String END_OF_PACKAGE_NAME = "([^a-zA-Z0-9_]|$)";
   1.205 +            // Regex for finding our package name in the traces file
   1.206 +            Pattern pkgPattern = Pattern.compile(Pattern.quote(pkgName) + END_OF_PACKAGE_NAME);
   1.207 +            Pattern mangledPattern = null;
   1.208 +            if (!AppConstants.MANGLED_ANDROID_PACKAGE_NAME.equals(pkgName)) {
   1.209 +                mangledPattern = Pattern.compile(Pattern.quote(
   1.210 +                    AppConstants.MANGLED_ANDROID_PACKAGE_NAME) + END_OF_PACKAGE_NAME);
   1.211 +            }
   1.212 +            if (DEBUG) {
   1.213 +                Log.d(LOGTAG, "trying to match package: " + pkgName);
   1.214 +            }
   1.215 +            BufferedReader traces = new BufferedReader(
   1.216 +                new FileReader(tracesFile), TRACES_BLOCK_SIZE);
   1.217 +            try {
   1.218 +                for (int count = 0; count < LINES_TO_IDENTIFY_TRACES; count++) {
   1.219 +                    String line = traces.readLine();
   1.220 +                    if (DEBUG) {
   1.221 +                        Log.d(LOGTAG, "identifying line: " + String.valueOf(line));
   1.222 +                    }
   1.223 +                    if (line == null) {
   1.224 +                        if (DEBUG) {
   1.225 +                            Log.d(LOGTAG, "reached end of traces file");
   1.226 +                        }
   1.227 +                        return false;
   1.228 +                    }
   1.229 +                    if (pkgPattern.matcher(line).find()) {
   1.230 +                        // traces.txt file contains our package
   1.231 +                        return true;
   1.232 +                    }
   1.233 +                    if (mangledPattern != null && mangledPattern.matcher(line).find()) {
   1.234 +                        // traces.txt file contains our alternate package
   1.235 +                        return true;
   1.236 +                    }
   1.237 +                }
   1.238 +            } finally {
   1.239 +                traces.close();
   1.240 +            }
   1.241 +        } catch (IOException e) {
   1.242 +            // meh, can't even read from it right. just return false
   1.243 +        }
   1.244 +        return false;
   1.245 +    }
   1.246 +
   1.247 +    private static long getUptimeMins() {
   1.248 +
   1.249 +        long uptimeMins = (new File("/proc/self/stat")).lastModified();
   1.250 +        if (uptimeMins != 0L) {
   1.251 +            uptimeMins = (System.currentTimeMillis() - uptimeMins) / 1000L / 60L;
   1.252 +            if (DEBUG) {
   1.253 +                Log.d(LOGTAG, "uptime " + String.valueOf(uptimeMins));
   1.254 +            }
   1.255 +            return uptimeMins;
   1.256 +        } else if (DEBUG) {
   1.257 +            Log.d(LOGTAG, "could not get uptime");
   1.258 +        }
   1.259 +        return 0L;
   1.260 +    }
   1.261 +
   1.262 +    /*
   1.263 +        a saved telemetry ping file consists of JSON in the following format,
   1.264 +            {
   1.265 +                "reason": "android-anr-report",
   1.266 +                "slug": "<uuid-string>",
   1.267 +                "payload": <json-object>
   1.268 +            }
   1.269 +        for Android ANR, our JSON payload should look like,
   1.270 +            {
   1.271 +                "ver": 1,
   1.272 +                "simpleMeasurements": {
   1.273 +                    "uptime": <uptime>
   1.274 +                },
   1.275 +                "info": {
   1.276 +                    "reason": "android-anr-report",
   1.277 +                    "OS": "Android",
   1.278 +                    ...
   1.279 +                },
   1.280 +                "androidANR": "...",
   1.281 +                "androidLogcat": "..."
   1.282 +            }
   1.283 +    */
   1.284 +
   1.285 +    private static int writePingPayload(OutputStream ping,
   1.286 +                                        String payload) throws IOException {
   1.287 +        byte [] data = payload.getBytes(PING_CHARSET);
   1.288 +        ping.write(data);
   1.289 +        return data.length;
   1.290 +    }
   1.291 +
   1.292 +    private static void fillPingHeader(OutputStream ping, String slug)
   1.293 +            throws IOException {
   1.294 +
   1.295 +        // ping file header
   1.296 +        byte [] data = ("{" +
   1.297 +            "\"reason\":\"android-anr-report\"," +
   1.298 +            "\"slug\":" + JSONObject.quote(slug) + "," +
   1.299 +            "\"payload\":").getBytes(PING_CHARSET);
   1.300 +        ping.write(data);
   1.301 +        if (DEBUG) {
   1.302 +            Log.d(LOGTAG, "wrote ping header, size = " + String.valueOf(data.length));
   1.303 +        }
   1.304 +
   1.305 +        // payload start
   1.306 +        int size = writePingPayload(ping, ("{" +
   1.307 +            "\"ver\":1," +
   1.308 +            "\"simpleMeasurements\":{" +
   1.309 +                "\"uptime\":" + String.valueOf(getUptimeMins()) +
   1.310 +            "}," +
   1.311 +            "\"info\":{" +
   1.312 +                "\"reason\":\"android-anr-report\"," +
   1.313 +                "\"OS\":" + JSONObject.quote(SysInfo.getName()) + "," +
   1.314 +                "\"version\":\"" + String.valueOf(SysInfo.getVersion()) + "\"," +
   1.315 +                "\"appID\":" + JSONObject.quote(AppConstants.MOZ_APP_ID) + "," +
   1.316 +                "\"appVersion\":" + JSONObject.quote(AppConstants.MOZ_APP_VERSION)+ "," +
   1.317 +                "\"appName\":" + JSONObject.quote(AppConstants.MOZ_APP_BASENAME) + "," +
   1.318 +                "\"appBuildID\":" + JSONObject.quote(AppConstants.MOZ_APP_BUILDID) + "," +
   1.319 +                "\"appUpdateChannel\":" + JSONObject.quote(AppConstants.MOZ_UPDATE_CHANNEL) + "," +
   1.320 +                // Technically the platform build ID may be different, but we'll never know
   1.321 +                "\"platformBuildID\":" + JSONObject.quote(AppConstants.MOZ_APP_BUILDID) + "," +
   1.322 +                "\"locale\":" + JSONObject.quote(SysInfo.getLocale()) + "," +
   1.323 +                "\"cpucount\":" + String.valueOf(SysInfo.getCPUCount()) + "," +
   1.324 +                "\"memsize\":" + String.valueOf(SysInfo.getMemSize()) + "," +
   1.325 +                "\"arch\":" + JSONObject.quote(SysInfo.getArchABI()) + "," +
   1.326 +                "\"kernel_version\":" + JSONObject.quote(SysInfo.getKernelVersion()) + "," +
   1.327 +                "\"device\":" + JSONObject.quote(SysInfo.getDevice()) + "," +
   1.328 +                "\"manufacturer\":" + JSONObject.quote(SysInfo.getManufacturer()) + "," +
   1.329 +                "\"hardware\":" + JSONObject.quote(SysInfo.getHardware()) +
   1.330 +            "}," +
   1.331 +            "\"androidANR\":\""));
   1.332 +        if (DEBUG) {
   1.333 +            Log.d(LOGTAG, "wrote metadata, size = " + String.valueOf(size));
   1.334 +        }
   1.335 +
   1.336 +        // We are at the start of ANR data
   1.337 +    }
   1.338 +
   1.339 +    // Block is a section of the larger input stream, and we want to find pattern within
   1.340 +    // the stream. This is straightforward if the entire pattern is within one block;
   1.341 +    // however, if the pattern spans across two blocks, we have to match both the start of
   1.342 +    // the pattern in the first block and the end of the pattern in the second block.
   1.343 +    // * If pattern is found in block, this method returns the index at the end of the
   1.344 +    //   found pattern, which must always be > 0.
   1.345 +    // * If pattern is not found, it returns 0.
   1.346 +    // * If the start of the pattern matches the end of the block, it returns a number
   1.347 +    //   < 0, which equals the negated value of how many characters in pattern are already
   1.348 +    //   matched; when processing the next block, this number is passed in through
   1.349 +    //   prevIndex, and the rest of the characters in pattern are matched against the
   1.350 +    //   start of this second block. The method returns value > 0 if the rest of the
   1.351 +    //   characters match, or 0 if they do not.
   1.352 +    private static int getEndPatternIndex(String block, String pattern, int prevIndex) {
   1.353 +        if (pattern == null || block.length() < pattern.length()) {
   1.354 +            // Nothing to do
   1.355 +            return 0;
   1.356 +        }
   1.357 +        if (prevIndex < 0) {
   1.358 +            // Last block ended with a partial start; now match start of block to rest of pattern
   1.359 +            if (block.startsWith(pattern.substring(-prevIndex, pattern.length()))) {
   1.360 +                // Rest of pattern matches; return index at end of pattern
   1.361 +                return pattern.length() + prevIndex;
   1.362 +            }
   1.363 +            // Not a match; continue with normal search
   1.364 +        }
   1.365 +        // Did not find pattern in last block; see if entire pattern is inside this block
   1.366 +        int index = block.indexOf(pattern);
   1.367 +        if (index >= 0) {
   1.368 +            // Found pattern; return index at end of the pattern
   1.369 +            return index + pattern.length();
   1.370 +        }
   1.371 +        // Block does not contain the entire pattern, but see if the end of the block
   1.372 +        // contains the start of pattern. To do that, we see if block ends with the
   1.373 +        // first n-1 characters of pattern, the first n-2 characters of pattern, etc.
   1.374 +        for (index = block.length() - pattern.length() + 1; index < block.length(); index++) {
   1.375 +            // Using index as a start, see if the rest of block contains the start of pattern
   1.376 +            if (block.charAt(index) == pattern.charAt(0) &&
   1.377 +                block.endsWith(pattern.substring(0, block.length() - index))) {
   1.378 +                // Found partial match; return -(number of characters matched),
   1.379 +                // i.e. -1 for 1 character matched, -2 for 2 characters matched, etc.
   1.380 +                return index - block.length();
   1.381 +            }
   1.382 +        }
   1.383 +        return 0;
   1.384 +    }
   1.385 +
   1.386 +    // Copy the content of reader to ping;
   1.387 +    // copying stops when endPattern is found in the input stream
   1.388 +    private static int fillPingBlock(OutputStream ping,
   1.389 +                                     Reader reader, String endPattern)
   1.390 +            throws IOException {
   1.391 +
   1.392 +        int total = 0;
   1.393 +        int endIndex = 0;
   1.394 +        char [] block = new char[TRACES_BLOCK_SIZE];
   1.395 +        for (int size = reader.read(block); size >= 0; size = reader.read(block)) {
   1.396 +            String stringBlock = new String(block, 0, size);
   1.397 +            endIndex = getEndPatternIndex(stringBlock, endPattern, endIndex);
   1.398 +            if (endIndex > 0) {
   1.399 +                // Found end pattern; clip the string
   1.400 +                stringBlock = stringBlock.substring(0, endIndex);
   1.401 +            }
   1.402 +            String quoted = JSONObject.quote(stringBlock);
   1.403 +            total += writePingPayload(ping, quoted.substring(1, quoted.length() - 1));
   1.404 +            if (endIndex > 0) {
   1.405 +                // End pattern already found; return now
   1.406 +                break;
   1.407 +            }
   1.408 +        }
   1.409 +        return total;
   1.410 +    }
   1.411 +
   1.412 +    private static void fillPingFooter(OutputStream ping,
   1.413 +                                       boolean haveNativeStack)
   1.414 +            throws IOException {
   1.415 +
   1.416 +        // We are at the end of ANR data
   1.417 +
   1.418 +        int total = writePingPayload(ping, ("\"," +
   1.419 +                "\"androidLogcat\":\""));
   1.420 +
   1.421 +        try {
   1.422 +            // get the last 200 lines of logcat
   1.423 +            Process proc = (new ProcessBuilder())
   1.424 +                .command("/system/bin/logcat", "-v", "threadtime", "-t", "200", "-d", "*:D")
   1.425 +                .redirectErrorStream(true)
   1.426 +                .start();
   1.427 +            try {
   1.428 +                Reader procOut = new InputStreamReader(proc.getInputStream(), TRACES_CHARSET);
   1.429 +                int size = fillPingBlock(ping, procOut, null);
   1.430 +                if (DEBUG) {
   1.431 +                    Log.d(LOGTAG, "wrote logcat, size = " + String.valueOf(size));
   1.432 +                }
   1.433 +            } finally {
   1.434 +                proc.destroy();
   1.435 +            }
   1.436 +        } catch (IOException e) {
   1.437 +            // ignore because logcat is not essential
   1.438 +            Log.w(LOGTAG, e);
   1.439 +        }
   1.440 +
   1.441 +        if (haveNativeStack) {
   1.442 +            total += writePingPayload(ping, ("\"," +
   1.443 +                    "\"androidNativeStack\":"));
   1.444 +
   1.445 +            String nativeStack = String.valueOf(getNativeStack());
   1.446 +            int size = writePingPayload(ping, nativeStack);
   1.447 +            if (DEBUG) {
   1.448 +                Log.d(LOGTAG, "wrote native stack, size = " + String.valueOf(size));
   1.449 +            }
   1.450 +            total += size + writePingPayload(ping, "}");
   1.451 +        } else {
   1.452 +            total += writePingPayload(ping, "\"}");
   1.453 +        }
   1.454 +
   1.455 +        byte [] data = (
   1.456 +            "}").getBytes(PING_CHARSET);
   1.457 +        ping.write(data);
   1.458 +        if (DEBUG) {
   1.459 +            Log.d(LOGTAG, "wrote ping footer, size = " + String.valueOf(data.length + total));
   1.460 +        }
   1.461 +    }
   1.462 +
   1.463 +    private static void processTraces(Reader traces, File pingFile) {
   1.464 +
   1.465 +        // Unwinding is memory intensive; only unwind if we have enough memory
   1.466 +        boolean haveNativeStack = requestNativeStack(
   1.467 +            /* unwind */ SysInfo.getMemSize() >= 640);
   1.468 +        try {
   1.469 +            OutputStream ping = new BufferedOutputStream(
   1.470 +                new FileOutputStream(pingFile), TRACES_BLOCK_SIZE);
   1.471 +            try {
   1.472 +                fillPingHeader(ping, pingFile.getName());
   1.473 +                // Traces file has the format
   1.474 +                //    ----- pid xxx at xxx -----
   1.475 +                //    Cmd line: org.mozilla.xxx
   1.476 +                //    * stack trace *
   1.477 +                //    ----- end xxx -----
   1.478 +                //    ----- pid xxx at xxx -----
   1.479 +                //    Cmd line: com.android.xxx
   1.480 +                //    * stack trace *
   1.481 +                //    ...
   1.482 +                // If we end the stack dump at the first end marker,
   1.483 +                // only Fennec stacks will be dumped
   1.484 +                int size = fillPingBlock(ping, traces, "\n----- end");
   1.485 +                if (DEBUG) {
   1.486 +                    Log.d(LOGTAG, "wrote traces, size = " + String.valueOf(size));
   1.487 +                }
   1.488 +                fillPingFooter(ping, haveNativeStack);
   1.489 +                if (DEBUG) {
   1.490 +                    Log.d(LOGTAG, "finished creating ping file");
   1.491 +                }
   1.492 +                return;
   1.493 +            } finally {
   1.494 +                ping.close();
   1.495 +                if (haveNativeStack) {
   1.496 +                    releaseNativeStack();
   1.497 +                }
   1.498 +            }
   1.499 +        } catch (IOException e) {
   1.500 +            Log.w(LOGTAG, e);
   1.501 +        }
   1.502 +        // exception; delete ping file
   1.503 +        if (pingFile.exists()) {
   1.504 +            pingFile.delete();
   1.505 +        }
   1.506 +    }
   1.507 +
   1.508 +    private static void processTraces(File tracesFile, File pingFile) {
   1.509 +        try {
   1.510 +            Reader traces = new InputStreamReader(
   1.511 +                new FileInputStream(tracesFile), TRACES_CHARSET);
   1.512 +            try {
   1.513 +                processTraces(traces, pingFile);
   1.514 +            } finally {
   1.515 +                traces.close();
   1.516 +            }
   1.517 +        } catch (IOException e) {
   1.518 +            Log.w(LOGTAG, e);
   1.519 +        }
   1.520 +    }
   1.521 +
   1.522 +    @Override
   1.523 +    public void onReceive(Context context, Intent intent) {
   1.524 +        if (mPendingANR) {
   1.525 +            // we already processed an ANR without getting unstuck; skip this one
   1.526 +            if (DEBUG) {
   1.527 +                Log.d(LOGTAG, "skipping duplicate ANR");
   1.528 +            }
   1.529 +            return;
   1.530 +        }
   1.531 +        if (ThreadUtils.getUiHandler() != null) {
   1.532 +            mPendingANR = true;
   1.533 +            // detect when the main thread gets unstuck
   1.534 +            ThreadUtils.postToUiThread(new Runnable() {
   1.535 +                @Override
   1.536 +                public void run() {
   1.537 +                    // okay to reset mPendingANR on main thread
   1.538 +                    mPendingANR = false;
   1.539 +                    if (DEBUG) {
   1.540 +                        Log.d(LOGTAG, "yay we got unstuck!");
   1.541 +                    }
   1.542 +                }
   1.543 +            });
   1.544 +        }
   1.545 +        if (DEBUG) {
   1.546 +            Log.d(LOGTAG, "receiving " + String.valueOf(intent));
   1.547 +        }
   1.548 +        if (!ANR_ACTION.equals(intent.getAction())) {
   1.549 +            return;
   1.550 +        }
   1.551 +
   1.552 +        // make sure we have a good save location first
   1.553 +        File pingFile = getPingFile();
   1.554 +        if (DEBUG) {
   1.555 +            Log.d(LOGTAG, "using ping file: " + String.valueOf(pingFile));
   1.556 +        }
   1.557 +        if (pingFile == null) {
   1.558 +            return;
   1.559 +        }
   1.560 +
   1.561 +        File tracesFile = getTracesFile();
   1.562 +        if (DEBUG) {
   1.563 +            Log.d(LOGTAG, "using traces file: " + String.valueOf(tracesFile));
   1.564 +        }
   1.565 +        if (tracesFile == null) {
   1.566 +            return;
   1.567 +        }
   1.568 +
   1.569 +        // We get ANR intents from all ANRs in the system, but we only want Gecko ANRs
   1.570 +        if (!isGeckoTraces(context.getPackageName(), tracesFile)) {
   1.571 +            if (DEBUG) {
   1.572 +                Log.d(LOGTAG, "traces is not Gecko ANR");
   1.573 +            }
   1.574 +            return;
   1.575 +        }
   1.576 +        Log.i(LOGTAG, "processing Gecko ANR");
   1.577 +        processTraces(tracesFile, pingFile);
   1.578 +    }
   1.579 +}

mercurial