Bug 1433968 - Add CrashReporterService for GeckView r=jchen

The Fennec CrashReporter class is also renamed to
CrashReporterActivity. When running in Fennec, the Activity will be used
which retains what we do today, prompting for comments, email, etc. When
used in standalone GeckoView, we report the crash without user
interaction if the appropriate GeckoRuntimeSetting was set. The app will
want to ask for user permission at least once in order to set this.

We do not collect the URL, email, or logcat with GeckoView crashes.
Logcat and URL would be nice to have, but it's not clear what the API
for those would look like, and they can be addressed in followup
patches.

MozReview-Commit-ID: C5ROsUKreRe
This commit is contained in:
James Willcox 2018-04-19 15:12:14 -05:00
parent 1ddcfb2222
commit bc73d8b2e0
9 changed files with 457 additions and 55 deletions

View File

@ -122,7 +122,7 @@ android {
}
if (!mozconfig.substs.MOZ_CRASHREPORTER) {
exclude 'org/mozilla/gecko/CrashReporter.java'
exclude 'org/mozilla/gecko/CrashReporterActivity.java'
}
if (!mozconfig.substs.MOZ_NATIVE_DEVICES) {

View File

@ -64,7 +64,7 @@
</issue>
<!-- We fixed all "Registered" lint errors. However the current gradle plugin has a bug where
it ignores @SuppressLint annotations for this check. See CrashReporter class and
it ignores @SuppressLint annotations for this check. See CrashReporterActivity class and
https://code.google.com/p/android/issues/detail?id=204846 -->
<issue id="Registered" severity="warning" />

View File

@ -272,7 +272,7 @@
#include ../services/manifests/FxAccountAndroidManifest_activities.xml.in
#ifdef MOZ_CRASHREPORTER
<activity android:name="org.mozilla.gecko.CrashReporter"
<activity android:name="org.mozilla.gecko.CrashReporterActivity"
android:process="@ANDROID_PACKAGE_NAME@.CrashReporter"
android:label="@string/crash_reporter_title"
android:icon="@drawable/crash_reporter"

View File

@ -28,7 +28,6 @@ import java.security.MessageDigest;
import java.util.zip.GZIPOutputStream;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.gecko.mozglue.MinidumpAnalyzer;
import org.mozilla.gecko.telemetry.pingbuilders.TelemetryCrashPingBuilder;
@ -57,7 +56,7 @@ import android.widget.EditText;
// Registered: This activity is only registered in the manifest if MOZ_CRASHREPORTER is set.
// CutPasteId: This lint is not worth fixing. To fix it, cache all the findViewById results.
@SuppressLint("Registered,CutPasteId")
public class CrashReporter extends AppCompatActivity
public class CrashReporterActivity extends AppCompatActivity
{
private static final String LOGTAG = "GeckoCrashReporter";
@ -210,7 +209,7 @@ public class CrashReporter extends AppCompatActivity
final EditText commentsEditText = (EditText) findViewById(R.id.comment);
final EditText emailEditText = (EditText) findViewById(R.id.email);
// Load CrashReporter preferences to avoid redundant user input.
// Load CrashReporterActivity preferences to avoid redundant user input.
SharedPreferences prefs = GeckoSharedPrefs.forCrashReporter(this);
final boolean sendReport = prefs.getBoolean(PREFS_SEND_REPORT, true);
final boolean includeUrl = prefs.getBoolean(PREFS_INCLUDE_URL, false);
@ -271,7 +270,7 @@ public class CrashReporter extends AppCompatActivity
builder.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
CrashReporter.this.finish();
CrashReporterActivity.this.finish();
}
});
builder.show();
@ -293,7 +292,7 @@ public class CrashReporter extends AppCompatActivity
public void run() {
sendReport(mPendingMinidumpFile, mExtrasStringMap, mPendingExtrasFile);
}
}, "CrashReporter Thread").start();
}, "CrashReporterActivity Thread").start();
}
private void savePrefs() {

View File

@ -139,6 +139,10 @@ 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"

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mozilla.geckoview">
package="org.mozilla.geckoview">
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
@ -12,62 +13,88 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-feature android:name="android.hardware.location" android:required="false"/>
<uses-feature android:name="android.hardware.location.gps" android:required="false"/>
<uses-feature
android:name="android.hardware.location"
android:required="false"/>
<uses-feature
android:name="android.hardware.location.gps"
android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature
android:name="android.hardware.camera"
android:required="false"/>
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false"/>
<!-- #ifdef MOZ_WEBRTC -->
<!--
TODO preprocess AndroidManifest.xml so that we can
conditionally include WebRTC permissions based on MOZ_WEBRTC.
-->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
-->
<uses-feature
android:name="android.hardware.audio.low_latency"
android:required="false"/>
-->
<uses-feature
android:name="android.hardware.microphone"
android:required="false"/>
-->
<uses-feature
android:name="android.hardware.camera.any"
android:required="false"/>
-->
<!-- #endif -->
<!--#ifdef MOZ_WEBRTC-->
<!-- TODO preprocess AndroidManifest.xml so that we can
conditionally include WebRTC permissions based on MOZ_WEBRTC. -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>-->
<uses-feature android:name="android.hardware.audio.low_latency" android:required="false"/>-->
<uses-feature android:name="android.hardware.microphone" android:required="false"/>-->
<uses-feature android:name="android.hardware.camera.any" android:required="false"/>-->
<!--#endif-->
<!-- App requires OpenGL ES 2.0 -->
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true"/>
<application>
<!-- New child services must also be added to the Fennec AndroidManifest.xml.in -->
<service
android:name="org.mozilla.gecko.media.MediaManager"
android:enabled="true"
android:exported="false"
android:process=":media"
android:isolatedProcess="false">
android:name="org.mozilla.gecko.media.MediaManager"
android:enabled="true"
android:exported="false"
android:isolatedProcess="false"
android:process=":media">
</service>
<service
android:name="org.mozilla.gecko.process.GeckoServiceChildProcess$geckomediaplugin"
android:enabled="true"
android:exported="false"
android:process=":geckomediaplugin"
android:isolatedProcess="false">
android:name="org.mozilla.gecko.process.GeckoServiceChildProcess$geckomediaplugin"
android:enabled="true"
android:exported="false"
android:isolatedProcess="false"
android:process=":geckomediaplugin">
</service>
<service
android:name="org.mozilla.gecko.process.GeckoServiceChildProcess$tab"
android:enabled="true"
android:exported="false"
android:process=":tab"
android:isolatedProcess="false">
android:name="org.mozilla.gecko.process.GeckoServiceChildProcess$tab"
android:enabled="true"
android:exported="false"
android:isolatedProcess="false"
android:process=":tab">
</service>
<service
android:name="org.mozilla.gecko.gfx.SurfaceAllocatorService"
android:enabled="true"
android:exported="false"
android:isolatedProcess="false">
android:name="org.mozilla.gecko.gfx.SurfaceAllocatorService"
android:enabled="true"
android:exported="false"
android:isolatedProcess="false">
</service>
</application>
<service
android:name="org.mozilla.gecko.CrashReporterService"
android:exported="false"
android:process=":crashreporter">
</service>
</application>
</manifest>
</manifest>

View File

@ -297,7 +297,7 @@ public class CrashHandler implements Thread.UncaughtExceptionHandler {
final Context context = getAppContext();
final String javaPkg = getJavaPackageName();
final String pkg = getAppPackageName();
final String component = javaPkg + ".CrashReporter";
final String component = javaPkg + ".CrashReporterService";
final String action = javaPkg + ".reportCrash";
final ProcessBuilder pb;
@ -305,8 +305,7 @@ public class CrashHandler implements Thread.UncaughtExceptionHandler {
final Intent intent = new Intent(action);
intent.setComponent(new ComponentName(pkg, component));
intent.putExtra("minidumpPath", dumpFile);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
context.startService(intent);
return true;
}

View File

@ -0,0 +1,373 @@
package org.mozilla.gecko;
import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.gecko.mozglue.MinidumpAnalyzer;
import org.mozilla.gecko.util.INIParser;
import org.mozilla.gecko.util.INISection;
import org.mozilla.gecko.util.ProxySelector;
import android.app.IntentService;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
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.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPOutputStream;
public class CrashReporterService extends IntentService {
private static final String LOGTAG = "CrashReporter";
private static final String ACTION_REPORT_CRASH = "org.mozilla.gecko.reportCrash";
private static final String PASSED_MINI_DUMP_KEY = "minidumpPath";
private static final String PASSED_MINI_DUMP_SUCCESS_KEY = "minidumpSuccess";
private static final String MINI_DUMP_PATH_KEY = "upload_file_minidump";
private static final String PAGE_URL_KEY = "URL";
private static final String NOTES_KEY = "Notes";
private static final String SERVER_URL_KEY = "ServerURL";
private static final String CRASH_REPORT_SUFFIX = "/mozilla/Crash Reports/";
private static final String PENDING_SUFFIX = CRASH_REPORT_SUFFIX + "pending";
private static final String SUBMITTED_SUFFIX = CRASH_REPORT_SUFFIX + "submitted";
private File mPendingMinidumpFile;
private File mPendingExtrasFile;
private HashMap<String, String> mExtrasStringMap;
private boolean mMinidumpSucceeded;
public CrashReporterService() {
super("CrashReporterService");
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent == null || !intent.getAction().equals(ACTION_REPORT_CRASH)) {
Log.d(LOGTAG, "Invalid or unknown action");
return;
}
Class<?> reporterActivityCls = getFennecReporterActivity();
if (reporterActivityCls != null) {
intent.setClass(this, reporterActivityCls);
startActivity(intent);
return;
}
submitCrash(intent);
}
private Class<?> getFennecReporterActivity() {
try {
return Class.forName("org.mozilla.gecko.CrashReporterActivity");
} catch (ClassNotFoundException e) {
return null;
}
}
private boolean moveFile(File inFile, File outFile) {
Log.i(LOGTAG, "moving " + inFile + " to " + outFile);
if (inFile.renameTo(outFile))
return true;
try {
outFile.createNewFile();
Log.i(LOGTAG, "couldn't rename minidump file");
// so copy it instead
FileChannel inChannel = new FileInputStream(inFile).getChannel();
FileChannel outChannel = new FileOutputStream(outFile).getChannel();
long transferred = inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
outChannel.close();
if (transferred > 0)
inFile.delete();
} catch (Exception e) {
Log.e(LOGTAG, "exception while copying minidump file: ", e);
return false;
}
return true;
}
private void submitCrash(Intent intent) {
mMinidumpSucceeded = intent.getBooleanExtra(PASSED_MINI_DUMP_SUCCESS_KEY, false);
if (!mMinidumpSucceeded) {
Log.i(LOGTAG, "Failed to get minidump.");
}
String passedMinidumpPath = intent.getStringExtra(PASSED_MINI_DUMP_KEY);
File passedMinidumpFile = new File(passedMinidumpPath);
File pendingDir = new File(getFilesDir(), PENDING_SUFFIX);
pendingDir.mkdirs();
mPendingMinidumpFile = new File(pendingDir, passedMinidumpFile.getName());
moveFile(passedMinidumpFile, mPendingMinidumpFile);
File extrasFile = new File(passedMinidumpPath.replaceAll("\\.dmp", ".extra"));
mPendingExtrasFile = new File(pendingDir, extrasFile.getName());
moveFile(extrasFile, mPendingExtrasFile);
// Compute the minidump hash and generate the stack traces
computeMinidumpHash(mPendingExtrasFile, mPendingMinidumpFile);
try {
GeckoLoader.loadMozGlue(this);
if (!MinidumpAnalyzer.GenerateStacks(mPendingMinidumpFile.getPath(), /* fullStacks */ false)) {
Log.e(LOGTAG, "Could not generate stacks for this minidump: " + passedMinidumpPath);
}
} catch (UnsatisfiedLinkError e) {
Log.e(LOGTAG, "Could not load libmozglue.so, stacks for this crash won't be generated");
}
// Extract the annotations from the .extra file
mExtrasStringMap = new HashMap<String, String>();
readStringsFromFile(mPendingExtrasFile.getPath(), mExtrasStringMap);
try {
// Find the profile name and path. Since we don't have any other way of getting it within
// this context we extract it from the crash dump path.
final File profileDir = passedMinidumpFile.getParentFile().getParentFile();
final String profileName = getProfileName(profileDir);
if (profileName != null) {
// Extract the crash dump ID and telemetry client ID, we need profile access for the latter.
final String passedMinidumpName = passedMinidumpFile.getName();
// Strip the .dmp suffix from the minidump name to obtain the crash ID.
final String crashId = passedMinidumpName.substring(0, passedMinidumpName.length() - 4);
final GeckoProfile profile = GeckoProfile.get(this, profileName, profileDir);
final String clientId = profile.getClientId();
}
} catch (GeckoProfileDirectories.NoMozillaDirectoryException | IOException e) {
Log.e(LOGTAG, "Cannot send the crash ping: ", e);
}
// Notify GeckoApp that we've crashed, so it can react appropriately during the next start.
try {
File crashFlag = new File(GeckoProfileDirectories.getMozillaDirectory(this), "CRASHED");
crashFlag.createNewFile();
} catch (GeckoProfileDirectories.NoMozillaDirectoryException | IOException e) {
Log.e(LOGTAG, "Cannot set crash flag: ", e);
}
sendReport(mPendingMinidumpFile, mExtrasStringMap, mPendingExtrasFile);
}
private String getProfileName(File profileDir) throws GeckoProfileDirectories.NoMozillaDirectoryException {
final File mozillaDir = GeckoProfileDirectories.getMozillaDirectory(this);
final INIParser parser = GeckoProfileDirectories.getProfilesINI(mozillaDir);
String profileName = null;
if (parser.getSections() != null) {
for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements(); ) {
final INISection section = e.nextElement();
final String path = section.getStringProperty("Path");
final boolean isRelative = (section.getIntProperty("IsRelative") == 1);
if ((isRelative && path.equals(profileDir.getName())) ||
path.equals(profileDir.getPath())) {
profileName = section.getStringProperty("Name");
break;
}
}
}
return profileName;
}
private 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 boolean readStringsFromFile(String filePath, Map<String, String> stringMap) {
try {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
return readStringsFromReader(reader, stringMap);
} catch (Exception e) {
Log.e(LOGTAG, "exception while reading strings: ", e);
return false;
}
}
private boolean readStringsFromReader(BufferedReader reader, Map<String, String> stringMap) throws IOException {
String line;
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));
stringMap.put(key, val);
}
}
reader.close();
return true;
}
private 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 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 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 void sendReport(File minidumpFile, Map<String, String> extras, File extrasFile) {
Log.i(LOGTAG, "sendReport: " + minidumpFile.getPath());
String spec = extras.get(SERVER_URL_KEY);
if (spec == null) {
return;
}
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());
HttpURLConnection 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(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);
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, PASSED_MINI_DUMP_SUCCESS_KEY, mMinidumpSucceeded ? "True" : "False");
sendFile(os, boundary, MINI_DUMP_PATH_KEY, minidumpFile);
os.write(("\r\n--" + boundary + "--\r\n").getBytes());
os.flush();
os.close();
BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
HashMap<String, String> responseMap = new HashMap<String, String>();
readStringsFromReader(br, responseMap);
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
File submittedDir = new File(getFilesDir(),
SUBMITTED_SUFFIX);
submittedDir.mkdirs();
minidumpFile.delete();
extrasFile.delete();
String crashid = responseMap.get("CrashID");
File file = new File(submittedDir, crashid + ".txt");
FileOutputStream fos = new FileOutputStream(file);
fos.write("Crash ID: ".getBytes());
fos.write(crashid.getBytes());
fos.close();
Log.i(LOGTAG, "Successfully sent crash report: " + crashid);
} else {
Log.w(LOGTAG, "Received failure HTTP response code from server: " + conn.getResponseCode());
}
} catch (IOException e) {
Log.e(LOGTAG, "exception during send: ", e);
} catch (URISyntaxException e) {
Log.e(LOGTAG, "exception during new URI: ", e);
}
}
private String unescape(String string) {
return string.replaceAll("\\\\\\\\", "\\").replaceAll("\\\\n", "\n").replaceAll("\\\\t", "\t");
}
}

View File

@ -840,7 +840,7 @@ LaunchCrashReporterActivity(XP_CHAR* aProgramPath, XP_CHAR* aMinidumpPath,
if (androidUserSerial) {
Unused << execlp("/system/bin/am",
"/system/bin/am",
"start",
"startservice",
"--user", androidUserSerial,
"-a", "org.mozilla.gecko.reportCrash",
"-n", aProgramPath,
@ -850,7 +850,7 @@ LaunchCrashReporterActivity(XP_CHAR* aProgramPath, XP_CHAR* aMinidumpPath,
} else {
Unused << execlp("/system/bin/am",
"/system/bin/am",
"start",
"startservice",
"-a", "org.mozilla.gecko.reportCrash",
"-n", aProgramPath,
"--es", "minidumpPath", aMinidumpPath,
@ -1544,10 +1544,10 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory,
const char* androidPackageName = PR_GetEnv("MOZ_ANDROID_PACKAGE_NAME");
if (androidPackageName != nullptr) {
nsCString package(androidPackageName);
package.AppendLiteral("/org.mozilla.gecko.CrashReporter");
package.AppendLiteral("/org.mozilla.gecko.CrashReporterService");
crashReporterPath = ToNewCString(package);
} else {
nsCString package(ANDROID_PACKAGE_NAME "/org.mozilla.gecko.CrashReporter");
nsCString package(ANDROID_PACKAGE_NAME "/org.mozilla.gecko.CrashReporterService");
crashReporterPath = ToNewCString(package);
}
#endif // !defined(MOZ_WIDGET_ANDROID)