Bug 1467461 - Migrate CrashReportingService to JobIntentService; r?snorp
Use the fact that a JobIntentService is still a Service to keep most of the
previous implementation and method of starting CrashReportingService.
On 26+ devices it will be called with "start-foreground-service".
This ensures it can be started even from background and the crash reporting
process would work as before but ActivityManager will post an ANR error to
logcat after 5 seconds because we aren't calling Service.startForeground()
(which would mean a user visible notification).
Will use different Job Ids depending on if the app is Firefox Release or
Firefox Beta.
The Job Id will be passed to GeckoThread when first initializing and then be
made available to CrashHandler and nsExceptionHandler.cpp to be sent in the
Intent that starts the CrashReporterService.
MozReview-Commit-ID: GATl6Waa9St
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
@@ -229,16 +229,17 @@ public class GeckoApplication extends Ap
return createRuntime(context, null);
}
private static GeckoRuntimeSettings.Builder createSettingsBuilder() {
return new GeckoRuntimeSettings.Builder()
.javaCrashReportingEnabled(true)
.nativeCrashReportingEnabled(true)
+ .crashReportingJobId(JobIdsConstants.getIdForCrashReporter())
.arguments(getDefaultGeckoArgs());
}
public static GeckoRuntime createRuntime(@NonNull Context context,
@Nullable SafeIntent intent) {
if (sGeckoRuntime != null) {
throw new IllegalStateException("Already have a GeckoRuntime!");
}
--- a/mobile/android/base/java/org/mozilla/gecko/JobIdsConstants.java
+++ b/mobile/android/base/java/org/mozilla/gecko/JobIdsConstants.java
@@ -36,16 +36,18 @@ public class JobIdsConstants {
private static final int JOB_ID_FILE_CLEANUP = 1008;
private static final int JOB_ID_UPDATES_REGISTER = 1009;
private static final int JOB_ID_UPDATES_CHECK_FOR = 1010;
private static final int JOB_ID_UPDATES_DOWNLOAD = 1011;
private static final int JOB_ID_UPDATES_APPLY = 1012;
+ private static final int JOB_ID_CRASH_REPORTER = 1013;
+
public static int getIdForDlcStudyJob() {
return getIdWithOffset(JOB_ID_DLC_STUDY);
}
public static int getIdForDlcDownloadJob() {
return getIdWithOffset(JOB_ID_DLC_DOWNLOAD);
}
@@ -88,16 +90,20 @@ public class JobIdsConstants {
public static int getIdForUpdatesDownloadJob() {
return getIdWithOffset(JOB_ID_UPDATES_DOWNLOAD);
}
public static int getIdForUpdatesApplyJob() {
return getIdWithOffset(JOB_ID_UPDATES_APPLY);
}
+ public static int getIdForCrashReporter() {
+ return getIdWithOffset(JOB_ID_CRASH_REPORTER);
+ }
+
private static boolean isReleaseBuild() {
return AppConstants.RELEASE_OR_BETA;
}
private static int getIdWithOffset(final int jobIdUsedInRelease) {
return isReleaseBuild() ? jobIdUsedInRelease : jobIdUsedInRelease + BETA_OFFSET;
}
}
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
@@ -152,16 +152,17 @@ public class TestRunnerActivity extends
final Bundle extras = intent.getExtras();
if (extras != null) {
runtimeSettingsBuilder.extras(extras);
}
runtimeSettingsBuilder
.nativeCrashReportingEnabled(true)
.javaCrashReportingEnabled(true)
+ .crashReportingJobId(1024)
.consoleOutput(true);
sRuntime = GeckoRuntime.create(this, runtimeSettingsBuilder.build());
sRuntime.setDelegate(new GeckoRuntime.Delegate() {
@Override
public void onShutdown() {
mKillProcessOnDestroy = true;
finish();
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
@@ -1225,17 +1225,18 @@ public class GeckoSessionTestRule extend
runtimeSettingsBuilder.arguments(new String[] { "-purgecaches" })
.extras(InstrumentationRegistry.getArguments())
.remoteDebuggingEnabled(true)
.consoleOutput(true);
if (env.isAutomation()) {
runtimeSettingsBuilder
.nativeCrashReportingEnabled(true)
- .javaCrashReportingEnabled(true);
+ .javaCrashReportingEnabled(true)
+ .crashReportingJobId(1024);
}
sRuntime = GeckoRuntime.create(
InstrumentationRegistry.getTargetContext(),
runtimeSettingsBuilder.build());
sRuntime.setDelegate(new GeckoRuntime.Delegate() {
@Override
--- a/mobile/android/geckoview/src/main/AndroidManifest.xml
+++ b/mobile/android/geckoview/src/main/AndroidManifest.xml
@@ -75,14 +75,16 @@
<service
android:name="org.mozilla.gecko.gfx.SurfaceAllocatorService"
android:enabled="true"
android:exported="false"
android:isolatedProcess="false">
</service>
<service
android:name="org.mozilla.gecko.CrashReporterService"
+ android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"
android:process=":crashreporter">
</service>
+
</application>
</manifest>
\ No newline at end of file
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/CrashHandler.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/CrashHandler.java
@@ -1,39 +1,38 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Process;
+import android.util.Log;
+
+import org.mozilla.geckoview.BuildConfig;
+
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.UUID;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Process;
-import android.util.Log;
-
-import org.mozilla.geckoview.BuildConfig;
-
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static final String LOGTAG = "GeckoCrashHandler";
private static final Thread MAIN_THREAD = Thread.currentThread();
private static final String DEFAULT_SERVER_URL =
"https://crash-reports.mozilla.com/submit?id=%1$s&version=%2$s&buildid=%3$s";
// Context for getting device information
@@ -294,38 +293,51 @@ public class CrashHandler implements Thr
protected boolean launchCrashReporter(final String dumpFile, final String extraFile) {
try {
final Context context = getAppContext();
final String javaPkg = getJavaPackageName();
final String pkg = getAppPackageName();
final String component = javaPkg + ".CrashReporterService";
final String action = javaPkg + ".reportCrash";
final ProcessBuilder pb;
+ final int crashReporterJobId = GeckoThread.getCrashReporterJobId();
if (context != null) {
final Intent intent = new Intent(action);
- intent.setComponent(new ComponentName(pkg, component));
+ intent.putExtra("jobId", crashReporterJobId);
intent.putExtra("minidumpPath", dumpFile);
- context.startService(intent);
+ CrashReporterService.enqueueWork(context, intent);
return true;
}
- if (Build.VERSION.SDK_INT < 17) {
+ final int deviceSdkVersion = Build.VERSION.SDK_INT;
+ if (deviceSdkVersion < 17) {
pb = new ProcessBuilder(
- "/system/bin/am", "start",
+ "/system/bin/am",
+ "startservice",
"-a", action,
"-n", pkg + '/' + component,
- "--es", "minidumpPath", dumpFile);
+ "--es", "minidumpPath", dumpFile,
+ "--ei", "jobId", String.valueOf(crashReporterJobId));
} else {
+ final String startServiceCommand;
+ if (deviceSdkVersion >= 26) {
+ startServiceCommand = "start-foreground-service";
+ } else {
+ startServiceCommand = "startservice";
+ }
+
pb = new ProcessBuilder(
- "/system/bin/am", "start",
+ "/system/bin/am",
+ startServiceCommand,
"--user", /* USER_CURRENT_OR_SELF */ "-3",
"-a", action,
"-n", pkg + '/' + component,
- "--es", "minidumpPath", dumpFile);
+ "--es", "minidumpPath", dumpFile,
+ "--ei", "jobId", String.valueOf(crashReporterJobId));
}
pb.start().waitFor();
} catch (final IOException e) {
Log.e(LOGTAG, "Error launching crash reporter", e);
return false;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/CrashReporterService.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/CrashReporterService.java
@@ -1,20 +1,26 @@
package org.mozilla.gecko;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.JobIntentService;
+import android.util.Log;
+
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 org.mozilla.geckoview.GeckoRuntime;
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;
@@ -28,17 +34,17 @@ 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 {
+public class CrashReporterService extends JobIntentService {
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";
@@ -47,38 +53,60 @@ public class CrashReporterService extend
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)) {
+ protected void onHandleWork(Intent intent) {
+ if (intent == null || intent.getAction() == 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);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
return;
}
submitCrash(intent);
}
+ @Override
+ public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
+ // When running on pre-O devices the work will be dispatched to onHandleWork() automatically
+ // When running on Oreo and later devices we will enqueue the work for the JobIntentService to perform
+ if (Build.VERSION.SDK_INT >= 26) {
+ // Only when the system restarts the service because of Service.START_STICKY
+ // the intent will be null. So the intent is safe to be passed to the JobIntentService.
+ enqueueWork(this, intent);
+ }
+
+ return Service.START_NOT_STICKY;
+ }
+
+ @Override
+ public boolean onStopCurrentWork() {
+ // If JobScheduler stopped execution before work being completed it should not be restarted.
+ return false;
+ }
+
+ static void enqueueWork(@NonNull final Context context, @NonNull final Intent intent) {
+ // Speculative default value. Should be unique across all Job Ids used inside the app.
+ final int jobId = intent.getIntExtra("jobId", 1024);
+
+ enqueueWork(context, CrashReporterService.class, jobId, intent);
+ }
+
private Class<?> getFennecReporterActivity() {
try {
return Class.forName("org.mozilla.gecko.CrashReporterActivity");
} catch (ClassNotFoundException e) {
return null;
}
}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
@@ -8,16 +8,17 @@ package org.mozilla.gecko;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.gecko.process.GeckoProcessManager;
import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.gecko.util.FileUtils;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.geckoview.BuildConfig;
+import org.mozilla.geckoview.GeckoRuntimeSettings;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -508,16 +509,25 @@ public class GeckoThread extends Thread
final GeckoBundle data = new GeckoBundle(1);
data.putBoolean("restart", restarting);
EventDispatcher.getInstance().dispatch("Gecko:Exited", data);
// Remove pumpMessageLoop() idle handler
Looper.myQueue().removeIdleHandler(idleHandler);
}
+ public static int getCrashReporterJobId() {
+ synchronized (INSTANCE) {
+ if (!INSTANCE.mInitialized) {
+ return 1024; // speculative, unique value
+ }
+ return INSTANCE.mExtras.getInt(GeckoRuntimeSettings.EXTRA_CRASH_REPORTING_JOB_ID, 1024);
+ }
+ }
+
@WrapForJNI(calledFrom = "gecko")
private static boolean pumpMessageLoop(final Message msg) {
final Handler geckoHandler = ThreadUtils.sGeckoHandler;
if (msg.obj == geckoHandler && msg.getTarget() == geckoHandler) {
// Our "queue is empty" message; see runGecko()
return false;
}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
@@ -16,16 +16,17 @@ 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";
private static File sCacheFile;
@@ -102,16 +103,19 @@ public final class GeckoLoader {
final File dataDir = new File(context.getApplicationInfo().dataDir);
putenv("MOZ_ANDROID_DATA_DIR=" + dataDir.getCanonicalPath());
} catch (final java.io.IOException e) {
Log.e(LOGTAG, "Failed to resolve app data directory");
}
putenv("MOZ_ANDROID_PACKAGE_NAME=" + context.getPackageName());
+ final int crashReporterJobId = GeckoThread.getCrashReporterJobId();
+ putenv("MOZ_ANDROID_CRASH_REPORTER_JOB_ID=" + crashReporterJobId);
+
setupDownloadEnvironment(context);
// profile home path
putenv("HOME=" + profilePath);
// setup the tmp path
File f = getTmpDir(context);
if (!f.exists()) {
@@ -133,16 +137,18 @@ public final class GeckoLoader {
putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + um.getSerialNumberForUser(android.os.Process.myUserHandle()));
} else {
Log.d(LOGTAG, "Unable to obtain user manager service on a device with SDK version " + Build.VERSION.SDK_INT);
}
}
putenv("LANG=" + Locale.getDefault().toString());
+ putenv("MOZ_ANDROID_DEVICE_SDK_VERSION=" + Build.VERSION.SDK_INT);
+
// env from extras could have reset out linker flags; set them again.
loadLibsSetupLocked(context);
}
private static void loadLibsSetupLocked(Context context) {
// setup the libs cache
putenv("GRE_HOME=" + getGREDir(context).getPath());
putenv("MOZ_LINKER_CACHE=" + getCacheDir(context).getPath());
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
@@ -1,35 +1,39 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* vim: ts=4 sw=4 expandtab:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.geckoview;
+import android.content.SharedPreferences;
import android.os.Parcel;
import android.os.Parcelable;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.GeckoThread;
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.ThreadUtils;
import java.io.File;
public final class GeckoRuntime implements Parcelable {
+ public static final String PREF_CRASH_REPORTING_JOB_ID = "PrefGeckoCrashReportingJobId";
+
private static final String LOGTAG = "GeckoRuntime";
private static final boolean DEBUG = false;
private static GeckoRuntime sDefaultRuntime;
/**
* Get the default runtime for the given context.
* This will create and initialize the runtime with the default settings.
@@ -43,17 +47,17 @@ public final class GeckoRuntime implemen
public static synchronized @NonNull GeckoRuntime getDefault(final @NonNull Context context) {
ThreadUtils.assertOnUiThread();
if (DEBUG) {
Log.d(LOGTAG, "getDefault");
}
if (sDefaultRuntime == null) {
sDefaultRuntime = new GeckoRuntime();
sDefaultRuntime.attachTo(context);
- sDefaultRuntime.init(new GeckoRuntimeSettings());
+ sDefaultRuntime.init(context, new GeckoRuntimeSettings());
}
return sDefaultRuntime;
}
private GeckoRuntimeSettings mSettings;
private Delegate mDelegate;
private RuntimeTelemetry mTelemetry;
@@ -79,17 +83,17 @@ public final class GeckoRuntime implemen
final EventCallback callback) {
if ("Gecko:Exited".equals(event) && mDelegate != null) {
mDelegate.onShutdown();
EventDispatcher.getInstance().unregisterUiThreadListener(mEventListener, "Gecko:Exited");
}
}
};
- /* package */ boolean init(final @NonNull GeckoRuntimeSettings settings) {
+ /* package */ boolean init(final @NonNull Context context, final @NonNull GeckoRuntimeSettings settings) {
if (DEBUG) {
Log.d(LOGTAG, "init");
}
int flags = 0;
if (settings.getUseContentProcessHint()) {
flags |= GeckoThread.FLAG_PRELOAD_CHILD;
}
@@ -100,16 +104,20 @@ public final class GeckoRuntime implemen
if (settings.getJavaCrashReportingEnabled()) {
flags |= GeckoThread.FLAG_ENABLE_JAVA_CRASHREPORTER;
}
if (settings.getPauseForDebuggerEnabled()) {
flags |= GeckoThread.FLAG_DEBUGGING;
}
+ final int crashReportingJobId = settings.getCrashReportingServiceJobId();
+ settings.getExtras().putInt(
+ GeckoRuntimeSettings.EXTRA_CRASH_REPORTING_JOB_ID, crashReportingJobId);
+
if (!GeckoThread.initMainProcess(/* profile */ null, settings.getArguments(),
settings.getExtras(), flags)) {
Log.w(LOGTAG, "init failed (could not initiate GeckoThread)");
return false;
}
if (!GeckoThread.launch()) {
Log.w(LOGTAG, "init failed (GeckoThread already launched)");
@@ -161,17 +169,17 @@ public final class GeckoRuntime implemen
ThreadUtils.assertOnUiThread();
if (DEBUG) {
Log.d(LOGTAG, "create " + context);
}
final GeckoRuntime runtime = new GeckoRuntime();
runtime.attachTo(context);
- if (!runtime.init(settings)) {
+ if (!runtime.init(context, settings)) {
throw new IllegalStateException("Failed to initialize GeckoRuntime");
}
return runtime;
}
/**
* Shutdown the runtime. This will invalidate all attached sessions.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
@@ -15,16 +15,21 @@ import android.os.Parcelable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate;
public final class GeckoRuntimeSettings implements Parcelable {
/**
+ * {@link #mExtras} key for the crash reporting job id.
+ */
+ public static final String EXTRA_CRASH_REPORTING_JOB_ID = "crashReporterJobId";
+
+ /**
* Settings builder used to construct the settings object.
*/
public static final class Builder {
private final GeckoRuntimeSettings mSettings;
public Builder() {
mSettings = new GeckoRuntimeSettings();
}
@@ -118,40 +123,59 @@ public final class GeckoRuntimeSettings
return this;
}
/**
* Set whether crash reporting for native code should be enabled. This will cause
* a SIGSEGV handler to be installed, and any crash encountered there will be
* reported to Mozilla.
*
+ * <br>If crash reporting is enabled {@link #crashReportingJobId(int)} must also be used.
+ *
* @param enabled A flag determining whether native crash reporting should be enabled.
* Defaults to false.
* @return This Builder.
*/
public @NonNull Builder nativeCrashReportingEnabled(final boolean enabled) {
mSettings.mNativeCrashReporting = enabled;
return this;
}
/**
* Set whether crash reporting for Java code should be enabled. This will cause
* a default unhandled exception handler to be installed, and any exceptions encountered
* will automatically reported to Mozilla.
*
+ * <br>If crash reporting is enabled {@link #crashReportingJobId(int)} must also be used.
+ *
* @param enabled A flag determining whether Java crash reporting should be enabled.
* Defaults to false.
* @return This Builder.
*/
public @NonNull Builder javaCrashReportingEnabled(final boolean enabled) {
mSettings.mJavaCrashReporting = enabled;
return this;
}
/**
+ * On Oreo and later devices we use the JobScheduler for crash reporting in the background.<br>
+ * This allows for setting the unique Job Id to be used.
+ * <a href="https://developer.android.com/reference/android/app/job/JobInfo.Builder#JobInfo.Builder(int,%20android.content.ComponentName)">
+ * See why it must be unique</a>
+ *
+ * @param id A unique integer.
+ *
+ * @return This Builder.
+ */
+ public @NonNull Builder crashReportingJobId(final int id) {
+ mSettings.mCrashReportingJobId = id;
+ return this;
+ }
+
+ /**
* Set whether there should be a pause during startup. This is useful if you need to
* wait for a debugger to attach.
*
* @param enabled A flag determining whether there will be a pause early in startup.
* Defaults to false.
* @return This Builder.
*/
public @NonNull Builder pauseForDebugger(boolean enabled) {
@@ -283,16 +307,17 @@ public final class GeckoRuntimeSettings
/* package */ Pref<String> mTrackingProtection = new Pref<String>(
"urlclassifier.trackingTable",
TrackingProtection.buildPrefValue(TrackingProtectionDelegate.CATEGORY_ALL));
/* package */ Pref<Boolean> mConsoleOutput = new Pref<Boolean>(
"geckoview.console.enabled", false);
/* package */ boolean mNativeCrashReporting;
/* package */ boolean mJavaCrashReporting;
+ /* package */ int mCrashReportingJobId;
/* package */ boolean mDebugPause;
private final Pref<?>[] mPrefs = new Pref<?>[] {
mCookieBehavior, mCookieLifetime, mCookieLifetimeDays, mConsoleOutput,
mJavaScript, mRemoteDebugging, mTrackingProtection, mWebFonts
};
/* package */ GeckoRuntimeSettings() {
@@ -321,16 +346,17 @@ public final class GeckoRuntimeSettings
// We know this is safe.
@SuppressWarnings("unchecked")
final Pref<Object> uncheckedPref = (Pref<Object>) mPrefs[i];
uncheckedPref.set(settings.mPrefs[i].get());
}
mNativeCrashReporting = settings.mNativeCrashReporting;
mJavaCrashReporting = settings.mJavaCrashReporting;
+ mCrashReportingJobId = settings.mCrashReportingJobId;
mDebugPause = settings.mDebugPause;
}
/* package */ void flush() {
for (final Pref<?> pref: mPrefs) {
pref.flush();
}
}
@@ -436,16 +462,23 @@ public final class GeckoRuntimeSettings
*
* @return True if Java crash reporting is enabled.
*/
public boolean getJavaCrashReportingEnabled() {
return mJavaCrashReporting;
}
/**
+ * Get the Job Id used on Oreo and later devices to manage crash reporting in background.
+ */
+ public int getCrashReportingServiceJobId() {
+ return mCrashReportingJobId;
+ }
+
+ /**
* Gets whether the pause-for-debugger is enabled or not.
*
* @return True if the pause is enabled.
*/
public boolean getPauseForDebuggerEnabled() { return mDebugPause; }
// Sync values with nsICookieService.idl.
@Retention(RetentionPolicy.SOURCE)
@@ -626,16 +659,17 @@ public final class GeckoRuntimeSettings
mExtras.writeToParcel(out, flags);
for (final Pref<?> pref : mPrefs) {
out.writeValue(pref.get());
}
ParcelableUtils.writeBoolean(out, mNativeCrashReporting);
ParcelableUtils.writeBoolean(out, mJavaCrashReporting);
+ out.writeInt(mCrashReportingJobId);
ParcelableUtils.writeBoolean(out, mDebugPause);
}
// AIDL code may call readFromParcel even though it's not part of Parcelable.
public void readFromParcel(final Parcel source) {
mUseContentProcess = ParcelableUtils.readBoolean(source);
mArgs = source.createStringArray();
mExtras.readFromParcel(source);
@@ -644,16 +678,17 @@ public final class GeckoRuntimeSettings
// We know this is safe.
@SuppressWarnings("unchecked")
final Pref<Object> uncheckedPref = (Pref<Object>) pref;
uncheckedPref.set(source.readValue(getClass().getClassLoader()));
}
mNativeCrashReporting = ParcelableUtils.readBoolean(source);
mJavaCrashReporting = ParcelableUtils.readBoolean(source);
+ mCrashReportingJobId = source.readInt();
mDebugPause = ParcelableUtils.readBoolean(source);
}
public static final Parcelable.Creator<GeckoRuntimeSettings> CREATOR
= new Parcelable.Creator<GeckoRuntimeSettings>() {
@Override
public GeckoRuntimeSettings createFromParcel(final Parcel in) {
final GeckoRuntimeSettings settings = new GeckoRuntimeSettings();
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
@@ -109,16 +109,17 @@ public class GeckoViewActivity extends A
if (extras != null) {
runtimeSettingsBuilder.extras(extras);
}
runtimeSettingsBuilder
.useContentProcessHint(mUseMultiprocess)
.remoteDebuggingEnabled(true)
.nativeCrashReportingEnabled(true)
.javaCrashReportingEnabled(true)
+ .crashReportingJobId(1024)
.consoleOutput(true)
.trackingProtectionCategories(TrackingProtectionDelegate.CATEGORY_ALL);
sGeckoRuntime = GeckoRuntime.create(this, runtimeSettingsBuilder.build());
}
mGeckoSession = (GeckoSession)getIntent().getParcelableExtra("session");
if (mGeckoSession != null) {
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -216,16 +216,23 @@ static time_t lastCrashTime = 0;
// The pathname of a file to store the crash time in
static XP_CHAR lastCrashTimeFilename[XP_PATH_MAX] = {0};
#if defined(MOZ_WIDGET_ANDROID)
// on Android 4.2 and above there is a user serial number associated
// with the current process that gets lost when we fork so we need to
// explicitly pass it to am
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
static Mutex* crashReporterAPILock;
static Mutex* notesFieldLock;
static AnnotationTable* crashReporterAPIData_Hash;
static nsCString* crashReporterAPIData = nullptr;
static nsCString* crashEventAPIData = nullptr;
@@ -837,30 +844,32 @@ LaunchCrashReporterActivity(XP_CHAR* aPr
if (pid == -1)
return false;
else if (pid == 0) {
// Invoke the reportCrash activity using am
if (androidUserSerial) {
Unused << execlp("/system/bin/am",
"/system/bin/am",
- "startservice",
+ androidStartServiceCommand,
"--user", androidUserSerial,
"-a", "org.mozilla.gecko.reportCrash",
"-n", aProgramPath,
"--es", "minidumpPath", aMinidumpPath,
+ "--ei", "jobId", androidCrashReporterJobId,
"--ez", "minidumpSuccess", aSucceeded ? "true" : "false",
(char*)0);
} else {
Unused << execlp("/system/bin/am",
"/system/bin/am",
- "startservice",
+ androidStartServiceCommand,
"-a", "org.mozilla.gecko.reportCrash",
"-n", aProgramPath,
"--es", "minidumpPath", aMinidumpPath,
+ "--ei", "jobId", androidCrashReporterJobId,
"--ez", "minidumpSuccess", aSucceeded ? "true" : "false",
(char*)0);
}
_exit(1);
} else {
// We need to wait on the 'am start' command above to finish, otherwise everything will
// be killed by the ActivityManager as soon as the signal handler exits
@@ -1547,16 +1556,31 @@ nsresult SetExceptionHandler(nsIFile* aX
if (androidPackageName != nullptr) {
nsCString package(androidPackageName);
package.AppendLiteral("/org.mozilla.gecko.CrashReporterService");
crashReporterPath = ToNewCString(package);
} else {
nsCString package(ANDROID_PACKAGE_NAME "/org.mozilla.gecko.CrashReporterService");
crashReporterPath = ToNewCString(package);
}
+
+ const char *deviceAndroidVersion = PR_GetEnv("MOZ_ANDROID_DEVICE_SDK_VERSION");
+ if (deviceAndroidVersion != nullptr) {
+ const int deviceSdkVersion = atol(deviceAndroidVersion);
+ if (deviceSdkVersion >= 26) {
+ androidStartServiceCommand = (char*)"start-foreground-service";
+ } else {
+ 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
#if defined(XP_WIN32)
nsString tempPath;
#else
nsCString tempPath;
#endif