зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1483329 - Add crash handling API to GeckoRuntime r=jchen,esawin
This commit is contained in:
Родитель
6d9557476c
Коммит
91381ffcaa
|
@ -145,10 +145,6 @@ android {
|
|||
exclude 'org/mozilla/gecko/media/Utils.java'
|
||||
}
|
||||
|
||||
if (!mozconfig.substs.MOZ_CRASHREPORTER) {
|
||||
exclude 'org/mozilla/gecko/CrashReporterService.java'
|
||||
}
|
||||
|
||||
if (mozconfig.substs.MOZ_WEBRTC) {
|
||||
srcDir "${topsrcdir}/media/webrtc/trunk/webrtc/base/java/src"
|
||||
srcDir "${topsrcdir}/media/webrtc/trunk/webrtc/modules/audio_device/android/java/src"
|
||||
|
|
|
@ -40,6 +40,7 @@ import org.mozilla.geckoview.BuildConfig;
|
|||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
|
@ -164,6 +165,15 @@ public class GeckoAppShell
|
|||
return sCrashHandler;
|
||||
}
|
||||
|
||||
private static Class<? extends Service> sCrashHandlerService;
|
||||
public static synchronized void setCrashHandlerService(final Class<? extends Service> handlerService) {
|
||||
sCrashHandlerService = handlerService;
|
||||
}
|
||||
|
||||
public static synchronized Class<? extends Service> getCrashHandlerService() {
|
||||
return sCrashHandlerService;
|
||||
}
|
||||
|
||||
public static synchronized boolean isCrashHandlingEnabled() {
|
||||
return sCrashHandler != null;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,16 @@
|
|||
|
||||
package org.mozilla.gecko.mozglue;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.annotation.JNITarget;
|
||||
import org.mozilla.gecko.annotation.RobocopTarget;
|
||||
import org.mozilla.geckoview.BuildConfig;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
|
@ -13,19 +23,6 @@ import java.util.Locale;
|
|||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import java.util.ArrayList;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.GeckoThread;
|
||||
import org.mozilla.gecko.annotation.JNITarget;
|
||||
import org.mozilla.gecko.annotation.RobocopTarget;
|
||||
import org.mozilla.geckoview.BuildConfig;
|
||||
|
||||
public final class GeckoLoader {
|
||||
private static final String LOGTAG = "GeckoLoader";
|
||||
|
||||
|
@ -139,6 +136,12 @@ public final class GeckoLoader {
|
|||
|
||||
putenv("LANG=" + Locale.getDefault().toString());
|
||||
|
||||
final Class<?> crashHandler = GeckoAppShell.getCrashHandlerService();
|
||||
if (crashHandler != null) {
|
||||
putenv("MOZ_ANDROID_CRASH_HANDLER=" +
|
||||
context.getPackageName() + "/" + crashHandler.getName());
|
||||
}
|
||||
|
||||
putenv("MOZ_ANDROID_DEVICE_SDK_VERSION=" + Build.VERSION.SDK_INT);
|
||||
|
||||
// env from extras could have reset out linker flags; set them again.
|
||||
|
|
|
@ -0,0 +1,311 @@
|
|||
package org.mozilla.geckoview;
|
||||
|
||||
import org.mozilla.gecko.util.ProxySelector;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
/**
|
||||
* Sends a crash report to the Mozilla <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
|
||||
* crash report server.
|
||||
*/
|
||||
public class CrashReporter {
|
||||
private static final String LOGTAG = "GeckoCrashReporter";
|
||||
private static final String MINI_DUMP_PATH_KEY = "upload_file_minidump";
|
||||
private static final String MINI_DUMP_SUCCESS_KEY = "minidumpSuccess";
|
||||
private static final String PAGE_URL_KEY = "URL";
|
||||
private static final String NOTES_KEY = "Notes";
|
||||
private static final String SERVER_URL_KEY = "ServerURL";
|
||||
|
||||
/**
|
||||
* Sends a crash report to the Mozilla <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
|
||||
* crash report server.
|
||||
*
|
||||
* @param context The current Context
|
||||
* @param intent The Intent sent to the {@link GeckoRuntime} crash handler
|
||||
* @throws IOException This can be thrown if there was a networking error while sending the report.
|
||||
* @throws URISyntaxException This can be thrown if the crash server URI from the extra data was invalid.
|
||||
* @see GeckoRuntimeSettings.Builder#crashHandler(Class)
|
||||
* @see GeckoRuntime#ACTION_CRASHED
|
||||
*/
|
||||
public static void sendCrashReport(Context context, Intent intent)
|
||||
throws IOException, URISyntaxException {
|
||||
sendCrashReport(context, intent.getExtras());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a crash report to the Mozilla <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
|
||||
* crash report server.
|
||||
*
|
||||
* @param context The current Context
|
||||
* @param intentExtras The Bundle of extras attached to the Intent received by a crash handler.
|
||||
* @throws IOException This can be thrown if there was a networking error while sending the report.
|
||||
* @throws URISyntaxException This can be thrown if the crash server URI from the extra data was invalid.
|
||||
* @see GeckoRuntimeSettings.Builder#crashHandler(Class)
|
||||
* @see GeckoRuntime#ACTION_CRASHED
|
||||
*/
|
||||
public static void sendCrashReport(Context context, Bundle intentExtras)
|
||||
throws IOException, URISyntaxException {
|
||||
final File dumpFile = new File(intentExtras.getString(GeckoRuntime.EXTRA_MINIDUMP_PATH));
|
||||
final File extrasFile = new File(intentExtras.getString(GeckoRuntime.EXTRA_EXTRAS_PATH));
|
||||
final boolean success = intentExtras.getBoolean(GeckoRuntime.EXTRA_MINIDUMP_SUCCESS, false);
|
||||
|
||||
sendCrashReport(context, dumpFile, extrasFile, success);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a crash report to the Mozilla <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
|
||||
* crash report server.
|
||||
*
|
||||
* @param context The current {@link Context}
|
||||
* @param minidumpFile A {@link File} referring to the minidump.
|
||||
* @param extrasFile A {@link File} referring to the extras file.
|
||||
* @param success A boolean indicating whether the dump was successfully generated.
|
||||
* @throws IOException This can be thrown if there was a networking error while sending the report.
|
||||
* @throws URISyntaxException This can be thrown if the crash server URI from the extra data was invalid.
|
||||
* @see GeckoRuntimeSettings.Builder#crashHandler(Class)
|
||||
* @see GeckoRuntime#ACTION_CRASHED
|
||||
*/
|
||||
public static void sendCrashReport(Context context, File minidumpFile, File extrasFile, boolean success) throws IOException, URISyntaxException {
|
||||
// Compute the minidump hash and generate the stack traces
|
||||
computeMinidumpHash(extrasFile, minidumpFile);
|
||||
|
||||
// Extract the annotations from the .extra file
|
||||
HashMap<String, String> extrasMap = readStringsFromFile(extrasFile.getPath());
|
||||
|
||||
sendCrashReport(context, minidumpFile, extrasMap, success);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a crash report to the Mozilla <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
|
||||
* crash report server.
|
||||
*
|
||||
* @param context The current {@link Context}
|
||||
* @param minidumpFile A {@link File} referring to the minidump.
|
||||
* @param extras A {@link HashMap} with the parsed key-value pairs from the extras file.
|
||||
* @param success A boolean indicating whether the dump was successfully generated.
|
||||
* @throws IOException This can be thrown if there was a networking error while sending the report.
|
||||
* @throws URISyntaxException This can be thrown if the crash server URI from the extra data was invalid.
|
||||
* @see GeckoRuntimeSettings.Builder#crashHandler(Class)
|
||||
* @see GeckoRuntime#ACTION_CRASHED
|
||||
*/
|
||||
public static void sendCrashReport(Context context, File minidumpFile, Map<String, String> extras, boolean success) throws IOException, URISyntaxException {
|
||||
Log.i(LOGTAG, "sendCrashReport: " + minidumpFile.getPath());
|
||||
|
||||
String spec = extras.get(SERVER_URL_KEY);
|
||||
if (spec == null) {
|
||||
return;
|
||||
}
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
final URL url = new URL(URLDecoder.decode(spec, "UTF-8"));
|
||||
final URI uri = new URI(url.getProtocol(), url.getUserInfo(),
|
||||
url.getHost(), url.getPort(),
|
||||
url.getPath(), url.getQuery(), url.getRef());
|
||||
conn = (HttpURLConnection) ProxySelector.openConnectionWithProxy(uri);
|
||||
conn.setRequestMethod("POST");
|
||||
String boundary = generateBoundary();
|
||||
conn.setDoOutput(true);
|
||||
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
|
||||
conn.setRequestProperty("Content-Encoding", "gzip");
|
||||
|
||||
OutputStream os = new GZIPOutputStream(conn.getOutputStream());
|
||||
for (String key : extras.keySet()) {
|
||||
if (key.equals(PAGE_URL_KEY)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!key.equals(SERVER_URL_KEY) && !key.equals(NOTES_KEY)) {
|
||||
sendPart(os, boundary, key, extras.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(extras.containsKey(NOTES_KEY) ? extras.get(NOTES_KEY) + "\n" : "");
|
||||
sb.append(Build.MANUFACTURER).append(' ')
|
||||
.append(Build.MODEL).append('\n')
|
||||
.append(Build.FINGERPRINT);
|
||||
sendPart(os, boundary, NOTES_KEY, sb.toString());
|
||||
|
||||
sendPart(os, boundary, "Android_Manufacturer", Build.MANUFACTURER);
|
||||
sendPart(os, boundary, "Android_Model", Build.MODEL);
|
||||
sendPart(os, boundary, "Android_Board", Build.BOARD);
|
||||
sendPart(os, boundary, "Android_Brand", Build.BRAND);
|
||||
sendPart(os, boundary, "Android_Device", Build.DEVICE);
|
||||
sendPart(os, boundary, "Android_Display", Build.DISPLAY);
|
||||
sendPart(os, boundary, "Android_Fingerprint", Build.FINGERPRINT);
|
||||
sendPart(os, boundary, "Android_CPU_ABI", Build.CPU_ABI);
|
||||
sendPart(os, boundary, "Android_PackageName", context.getPackageName());
|
||||
try {
|
||||
sendPart(os, boundary, "Android_CPU_ABI2", Build.CPU_ABI2);
|
||||
sendPart(os, boundary, "Android_Hardware", Build.HARDWARE);
|
||||
} catch (Exception ex) {
|
||||
Log.e(LOGTAG, "Exception while sending SDK version 8 keys", ex);
|
||||
}
|
||||
sendPart(os, boundary, "Android_Version", Build.VERSION.SDK_INT + " (" + Build.VERSION.CODENAME + ")");
|
||||
sendPart(os, boundary, MINI_DUMP_SUCCESS_KEY, success ? "True" : "False");
|
||||
sendFile(os, boundary, MINI_DUMP_PATH_KEY, minidumpFile);
|
||||
os.write(("\r\n--" + boundary + "--\r\n").getBytes());
|
||||
os.flush();
|
||||
os.close();
|
||||
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(
|
||||
new InputStreamReader(conn.getInputStream()));
|
||||
HashMap<String, String> responseMap = readStringsFromReader(br);
|
||||
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
String crashid = responseMap.get("CrashID");
|
||||
Log.i(LOGTAG, "Successfully sent crash report: " + crashid);
|
||||
} else {
|
||||
Log.w(LOGTAG, "Received failure HTTP response code from server: " + conn.getResponseCode());
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
if (br != null) {
|
||||
br.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void computeMinidumpHash(File extraFile, File minidump) {
|
||||
try {
|
||||
FileInputStream stream = new FileInputStream(minidump);
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
try {
|
||||
byte[] buffer = new byte[4096];
|
||||
int readBytes;
|
||||
|
||||
while ((readBytes = stream.read(buffer)) != -1) {
|
||||
md.update(buffer, 0, readBytes);
|
||||
}
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
|
||||
byte[] digest = md.digest();
|
||||
StringBuilder hash = new StringBuilder(84);
|
||||
|
||||
hash.append("MinidumpSha256Hash=");
|
||||
|
||||
for (int i = 0; i < digest.length; i++) {
|
||||
hash.append(Integer.toHexString((digest[i] & 0xf0) >> 4));
|
||||
hash.append(Integer.toHexString(digest[i] & 0x0f));
|
||||
}
|
||||
|
||||
hash.append('\n');
|
||||
|
||||
FileWriter writer = new FileWriter(extraFile, /* append */ true);
|
||||
|
||||
try {
|
||||
writer.write(hash.toString());
|
||||
} finally {
|
||||
writer.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "exception while computing the minidump hash: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static HashMap<String, String> readStringsFromFile(String filePath) throws IOException {
|
||||
FileReader fileReader = null;
|
||||
BufferedReader bufReader = null;
|
||||
try {
|
||||
fileReader = new FileReader(filePath);
|
||||
bufReader = new BufferedReader(fileReader);
|
||||
return readStringsFromReader(bufReader);
|
||||
} finally {
|
||||
try {
|
||||
if (fileReader != null) {
|
||||
fileReader.close();
|
||||
}
|
||||
|
||||
if (bufReader != null) {
|
||||
bufReader.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static HashMap<String, String> readStringsFromReader(BufferedReader reader) throws IOException {
|
||||
String line;
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
while ((line = reader.readLine()) != null) {
|
||||
int equalsPos = -1;
|
||||
if ((equalsPos = line.indexOf('=')) != -1) {
|
||||
String key = line.substring(0, equalsPos);
|
||||
String val = unescape(line.substring(equalsPos + 1));
|
||||
map.put(key, val);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static String generateBoundary() {
|
||||
// Generate some random numbers to fill out the boundary
|
||||
int r0 = (int)(Integer.MAX_VALUE * Math.random());
|
||||
int r1 = (int)(Integer.MAX_VALUE * Math.random());
|
||||
return String.format("---------------------------%08X%08X", r0, r1);
|
||||
}
|
||||
|
||||
private static void sendPart(OutputStream os, String boundary, String name, String data) {
|
||||
try {
|
||||
os.write(("--" + boundary + "\r\n" +
|
||||
"Content-Disposition: form-data; name=\"" + name + "\"\r\n" +
|
||||
"\r\n" +
|
||||
data + "\r\n"
|
||||
).getBytes());
|
||||
} catch (Exception ex) {
|
||||
Log.e(LOGTAG, "Exception when sending \"" + name + "\"", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendFile(OutputStream os, String boundary, String name, File file) throws IOException {
|
||||
os.write(("--" + boundary + "\r\n" +
|
||||
"Content-Disposition: form-data; name=\"" + name + "\"; " +
|
||||
"filename=\"" + file.getName() + "\"\r\n" +
|
||||
"Content-Type: application/octet-stream\r\n" +
|
||||
"\r\n"
|
||||
).getBytes());
|
||||
FileChannel fc = new FileInputStream(file).getChannel();
|
||||
fc.transferTo(0, fc.size(), Channels.newChannel(os));
|
||||
fc.close();
|
||||
}
|
||||
|
||||
private static String unescape(String string) {
|
||||
return string.replaceAll("\\\\\\\\", "\\").replaceAll("\\\\n", "\n").replaceAll("\\\\t", "\t");
|
||||
}
|
||||
}
|
|
@ -6,11 +6,17 @@
|
|||
|
||||
package org.mozilla.geckoview;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.content.Context;
|
||||
import android.os.Process;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
|
@ -21,6 +27,7 @@ import org.mozilla.gecko.PrefsHelper;
|
|||
import org.mozilla.gecko.util.BundleEventListener;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -29,6 +36,53 @@ public final class GeckoRuntime implements Parcelable {
|
|||
private static final String LOGTAG = "GeckoRuntime";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* Intent action sent to the crash handler when a crash is encountered.
|
||||
* @see GeckoRuntimeSettings.Builder#crashHandler(Class)
|
||||
*/
|
||||
public static final String ACTION_CRASHED = "org.mozilla.gecko.ACTION_CRASHED";
|
||||
|
||||
/**
|
||||
* This is a key for extra data sent with {@link #ACTION_CRASHED}. It refers
|
||||
* to a String with the path to a Breakpad minidump file containing information about
|
||||
* the crash. Several crash reporters are able to ingest this in a
|
||||
* crash report, including <a href="https://sentry.io">Sentry</a>
|
||||
* and Mozilla's <a href="https://wiki.mozilla.org/Socorro">Socorro</a>.
|
||||
* <br><br>
|
||||
* Be aware, the minidump can contain personally identifiable information.
|
||||
* Ensure you are obeying all applicable laws and policies before sending
|
||||
* this to a remote server.
|
||||
* @see GeckoRuntimeSettings.Builder#crashHandler(Class)
|
||||
*/
|
||||
public static final String EXTRA_MINIDUMP_PATH = "minidumpPath";
|
||||
|
||||
/**
|
||||
* This is a key for extra data sent with {@link #ACTION_CRASHED}. It refers
|
||||
* to a string with the path to a file containing extra metadata about the crash. The file
|
||||
* contains key-value pairs in the form
|
||||
* <pre>Key=Value</pre>
|
||||
* Be aware, it may contain sensitive data such
|
||||
* as the URI that was loaded at the time of the crash.
|
||||
*/
|
||||
public static final String EXTRA_EXTRAS_PATH = "extrasPath";
|
||||
|
||||
/**
|
||||
* This is a key for extra data sent with {@link #ACTION_CRASHED}. The value is
|
||||
* a boolean indicating whether or not the crash dump was succcessfully
|
||||
* retrieved. If this is false, the dump file referred to in
|
||||
* {@link #EXTRA_MINIDUMP_PATH} may be corrupted or incomplete.
|
||||
*/
|
||||
public static final String EXTRA_MINIDUMP_SUCCESS = "minidumpSuccess";
|
||||
|
||||
/**
|
||||
* This is a key for extra data sent with {@link #ACTION_CRASHED}. The value is
|
||||
* a boolean indicating whether or not the crash was fatal or not. If true, the
|
||||
* main application process was affected by the crash. If false, only an internal
|
||||
* process used by Gecko has crashed and the application may be able to recover.
|
||||
* @see GeckoSession.ContentDelegate#onCrash(GeckoSession)
|
||||
*/
|
||||
public static final String EXTRA_CRASH_FATAL = "fatal";
|
||||
|
||||
private static GeckoRuntime sDefaultRuntime;
|
||||
|
||||
/**
|
||||
|
@ -85,6 +139,17 @@ public final class GeckoRuntime implements Parcelable {
|
|||
}
|
||||
};
|
||||
|
||||
private static final String getProcessName(Context context) {
|
||||
final ActivityManager manager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
for (final ActivityManager.RunningAppProcessInfo info : manager.getRunningAppProcesses()) {
|
||||
if (info.pid == Process.myPid()) {
|
||||
return info.processName;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* package */ boolean init(final @NonNull Context context, final @NonNull GeckoRuntimeSettings settings) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "init");
|
||||
|
@ -98,9 +163,28 @@ public final class GeckoRuntime implements Parcelable {
|
|||
flags |= GeckoThread.FLAG_DEBUGGING;
|
||||
}
|
||||
|
||||
final Class<?> crashHandler = settings.getCrashHandler();
|
||||
if (crashHandler != null) {
|
||||
try {
|
||||
final ServiceInfo info = context.getPackageManager().getServiceInfo(new ComponentName(context, crashHandler), 0);
|
||||
if (info.processName.equals(getProcessName(context))) {
|
||||
throw new IllegalArgumentException("Crash handler service must run in a separate process");
|
||||
}
|
||||
|
||||
flags |= GeckoThread.FLAG_ENABLE_NATIVE_CRASHREPORTER;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
throw new IllegalArgumentException("Crash handler must be registered as a service");
|
||||
}
|
||||
}
|
||||
|
||||
if (GeckoAppShell.isFennec()) {
|
||||
flags |= GeckoThread.FLAG_ENABLE_JAVA_CRASHREPORTER;
|
||||
}
|
||||
|
||||
GeckoAppShell.setDisplayDensityOverride(settings.getDisplayDensityOverride());
|
||||
GeckoAppShell.setDisplayDpiOverride(settings.getDisplayDpiOverride());
|
||||
GeckoAppShell.setScreenSizeOverride(settings.getScreenSizeOverride());
|
||||
GeckoAppShell.setCrashHandlerService(settings.getCrashHandler());
|
||||
|
||||
if (!GeckoThread.initMainProcess(/* profile */ null, settings.getArguments(),
|
||||
settings.getExtras(), flags)) {
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.mozilla.geckoview;
|
|||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import android.app.Service;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
|
@ -252,6 +253,44 @@ public final class GeckoRuntimeSettings implements Parcelable {
|
|||
mSettings.mScreenHeightOverride = height;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When set, the specified {@link android.app.Service} will be started by
|
||||
* an {@link android.content.Intent} with action {@link GeckoRuntime#ACTION_CRASHED} when
|
||||
* a crash is encountered. Crash details can be found in the Intent extras, such as
|
||||
* {@link GeckoRuntime#EXTRA_MINIDUMP_PATH}.
|
||||
* <br><br>
|
||||
* The crash handler Service must be declared to run in a different process from
|
||||
* the {@link GeckoRuntime}. Additionally, the handler will be run as a foreground service,
|
||||
* so the normal rules about activating a foreground service apply.
|
||||
* <br><br>
|
||||
* In practice, you have one of three
|
||||
* options once the crash handler is started:
|
||||
* <ul>
|
||||
* <li>Call {@link android.app.Service#startForeground(int, android.app.Notification)}. You can then
|
||||
* take as much time as necessary to report the crash.</li>
|
||||
* <li>Start an activity. Unless you also call {@link android.app.Service#startForeground(int, android.app.Notification)}
|
||||
* this should be in a different process from the crash handler, since Android will
|
||||
* kill the crash handler process as part of the background execution limitations.</li>
|
||||
* <li>Schedule work via {@link android.app.job.JobScheduler}. This will allow you to
|
||||
* do substantial work in the background without execution limits.</li>
|
||||
* </ul><br>
|
||||
* You can use {@link CrashReporter} to send the report to Mozilla, which provides Mozilla
|
||||
* with data needed to fix the crash. Be aware that the minidump may contain
|
||||
* personally identifiable information (PII). Consult Mozilla's
|
||||
* <a href="https://www.mozilla.org/en-US/privacy/">privacy policy</a> for information
|
||||
* on how this data will be handled.
|
||||
*
|
||||
* @param handler The class for the crash handler Service.
|
||||
* @return This builder instance.
|
||||
*
|
||||
* @see <a href="https://developer.android.com/about/versions/oreo/background">Android Background Execution Limits</a>
|
||||
* @see GeckoRuntime#ACTION_CRASHED
|
||||
*/
|
||||
public @NonNull Builder crashHandler(final Class<? extends Service> handler) {
|
||||
mSettings.mCrashHandler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ GeckoRuntime runtime;
|
||||
|
@ -320,6 +359,7 @@ public final class GeckoRuntimeSettings implements Parcelable {
|
|||
/* package */ int mDisplayDpiOverride;
|
||||
/* package */ int mScreenWidthOverride;
|
||||
/* package */ int mScreenHeightOverride;
|
||||
/* package */ Class<? extends Service> mCrashHandler;
|
||||
|
||||
private final Pref<?>[] mPrefs = new Pref<?>[] {
|
||||
mCookieBehavior, mCookieLifetime, mConsoleOutput,
|
||||
|
@ -361,6 +401,7 @@ public final class GeckoRuntimeSettings implements Parcelable {
|
|||
mDisplayDpiOverride = settings.mDisplayDpiOverride;
|
||||
mScreenWidthOverride = settings.mScreenWidthOverride;
|
||||
mScreenHeightOverride = settings.mScreenHeightOverride;
|
||||
mCrashHandler = settings.mCrashHandler;
|
||||
}
|
||||
|
||||
/* package */ void flush() {
|
||||
|
@ -487,6 +528,10 @@ public final class GeckoRuntimeSettings implements Parcelable {
|
|||
return null;
|
||||
}
|
||||
|
||||
public Class<? extends Service> getCrashHandler() {
|
||||
return mCrashHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the screen size override value.
|
||||
*
|
||||
|
@ -707,6 +752,7 @@ public final class GeckoRuntimeSettings implements Parcelable {
|
|||
out.writeInt(mDisplayDpiOverride);
|
||||
out.writeInt(mScreenWidthOverride);
|
||||
out.writeInt(mScreenHeightOverride);
|
||||
out.writeString(mCrashHandler != null ? mCrashHandler.getName() : null);
|
||||
}
|
||||
|
||||
// AIDL code may call readFromParcel even though it's not part of Parcelable.
|
||||
|
@ -727,6 +773,18 @@ public final class GeckoRuntimeSettings implements Parcelable {
|
|||
mDisplayDpiOverride = source.readInt();
|
||||
mScreenWidthOverride = source.readInt();
|
||||
mScreenHeightOverride = source.readInt();
|
||||
|
||||
final String crashHandlerName = source.readString();
|
||||
if (crashHandlerName != null) {
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
final Class<? extends Service> handler =
|
||||
(Class<? extends Service>) Class.forName(crashHandlerName);
|
||||
|
||||
mCrashHandler = handler;
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<GeckoRuntimeSettings> CREATOR
|
||||
|
|
|
@ -227,9 +227,6 @@ static char* androidUserSerial = nullptr;
|
|||
// Before Android 8 we needed to use "startservice" to start the crash reporting service.
|
||||
// After Android 8 we need to use "start-foreground-service"
|
||||
static const char* androidStartServiceCommand = nullptr;
|
||||
// After targeting API 26 (Oreo) we ned to use a JobIntentService for the background
|
||||
// work regarding crash reporting. That Service needs a unique Job Id.
|
||||
static const char* androidCrashReporterJobId = nullptr;
|
||||
#endif
|
||||
|
||||
// this holds additional data sent via the API
|
||||
|
@ -838,35 +835,43 @@ LaunchProgram(const XP_CHAR* aProgramPath, const XP_CHAR* aMinidumpPath)
|
|||
*/
|
||||
|
||||
static bool
|
||||
LaunchCrashReporterActivity(XP_CHAR* aProgramPath, XP_CHAR* aMinidumpPath,
|
||||
bool aSucceeded)
|
||||
LaunchCrashHandlerService(XP_CHAR* aProgramPath, XP_CHAR* aMinidumpPath,
|
||||
bool aSucceeded)
|
||||
{
|
||||
static XP_CHAR extrasPath[XP_PATH_MAX];
|
||||
size_t size = XP_PATH_MAX;
|
||||
|
||||
XP_CHAR* p = Concat(extrasPath, aMinidumpPath, &size);
|
||||
p = Concat(p - 3, "extra", &size);
|
||||
|
||||
pid_t pid = sys_fork();
|
||||
|
||||
if (pid == -1)
|
||||
return false;
|
||||
else if (pid == 0) {
|
||||
// Invoke the reportCrash activity using am
|
||||
// Invoke the crash handler service using am
|
||||
if (androidUserSerial) {
|
||||
Unused << execlp("/system/bin/am",
|
||||
"/system/bin/am",
|
||||
androidStartServiceCommand,
|
||||
"--user", androidUserSerial,
|
||||
"-a", "org.mozilla.gecko.reportCrash",
|
||||
"-a", "org.mozilla.gecko.ACTION_CRASHED",
|
||||
"-n", aProgramPath,
|
||||
"--es", "minidumpPath", aMinidumpPath,
|
||||
"--ei", "jobId", androidCrashReporterJobId,
|
||||
"--es", "extrasPath", extrasPath,
|
||||
"--ez", "minidumpSuccess", aSucceeded ? "true" : "false",
|
||||
"--ez", "fatal", "true",
|
||||
(char*)0);
|
||||
} else {
|
||||
Unused << execlp("/system/bin/am",
|
||||
"/system/bin/am",
|
||||
androidStartServiceCommand,
|
||||
"-a", "org.mozilla.gecko.reportCrash",
|
||||
"-a", "org.mozilla.gecko.ACTION_CRASHED",
|
||||
"-n", aProgramPath,
|
||||
"--es", "minidumpPath", aMinidumpPath,
|
||||
"--ei", "jobId", androidCrashReporterJobId,
|
||||
"--es", "extrasPath", extrasPath,
|
||||
"--ez", "minidumpSuccess", aSucceeded ? "true" : "false",
|
||||
"--ez", "fatal", "true",
|
||||
(char*)0);
|
||||
}
|
||||
_exit(1);
|
||||
|
@ -1132,8 +1137,8 @@ MinidumpCallback(
|
|||
}
|
||||
|
||||
#if defined(MOZ_WIDGET_ANDROID) // Android
|
||||
returnValue = LaunchCrashReporterActivity(crashReporterPath, minidumpPath,
|
||||
succeeded);
|
||||
returnValue = LaunchCrashHandlerService(crashReporterPath, minidumpPath,
|
||||
succeeded);
|
||||
#else // Windows, Mac, Linux, etc...
|
||||
returnValue = LaunchProgram(crashReporterPath, minidumpPath);
|
||||
#ifdef XP_WIN
|
||||
|
@ -1551,17 +1556,12 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory,
|
|||
#endif
|
||||
#endif // XP_WIN32
|
||||
#else
|
||||
// On Android, we launch using the application package name instead of a
|
||||
// filename, so use the dynamically set MOZ_ANDROID_PACKAGE_NAME, or fall
|
||||
// back to the static ANDROID_PACKAGE_NAME.
|
||||
const char* androidPackageName = PR_GetEnv("MOZ_ANDROID_PACKAGE_NAME");
|
||||
if (androidPackageName != nullptr) {
|
||||
nsCString package(androidPackageName);
|
||||
package.AppendLiteral("/org.mozilla.gecko.CrashReporterService");
|
||||
crashReporterPath = ToNewCString(package);
|
||||
// On Android, we launch a service defined via MOZ_ANDROID_CRASH_HANDLER
|
||||
const char* androidCrashHandler = PR_GetEnv("MOZ_ANDROID_CRASH_HANDLER");
|
||||
if (androidCrashHandler) {
|
||||
crashReporterPath = strdup(androidCrashHandler);
|
||||
} else {
|
||||
nsCString package(ANDROID_PACKAGE_NAME "/org.mozilla.gecko.CrashReporterService");
|
||||
crashReporterPath = ToNewCString(package);
|
||||
NS_WARNING("No Android crash handler set");
|
||||
}
|
||||
|
||||
const char *deviceAndroidVersion = PR_GetEnv("MOZ_ANDROID_DEVICE_SDK_VERSION");
|
||||
|
@ -1573,11 +1573,6 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory,
|
|||
androidStartServiceCommand = (char*)"startservice";
|
||||
}
|
||||
}
|
||||
|
||||
const char *crashReporterJobId = PR_GetEnv("MOZ_ANDROID_CRASH_REPORTER_JOB_ID");
|
||||
if (crashReporterJobId != nullptr) {
|
||||
androidCrashReporterJobId = crashReporterJobId;
|
||||
}
|
||||
#endif // !defined(MOZ_WIDGET_ANDROID)
|
||||
|
||||
// get temp path to use for minidump path
|
||||
|
|
Загрузка…
Ссылка в новой задаче