Bug 1467461 - Migrate CrashReportingService to JobIntentService; r?snorp draft
authorPetru Lingurar <petru.lingurar@softvision.ro>
Mon, 02 Jul 2018 17:32:10 +0300
changeset 817979 b6dd3347bba6f86e934ca0b256b3629f7eed39ef
parent 817978 51994cdd3714040d2e834c5125d057e365b5fba3
child 817980 859985b9eb7b9dd6c69b9eb4b9534896dfa053f6
child 818690 d5424b78fcc5e33da03574dd36523ea53376a2d5
push id116231
push userplingurar@mozilla.com
push dateFri, 13 Jul 2018 19:23:06 +0000
reviewerssnorp
bugs1467461
milestone63.0a1
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
mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
mobile/android/base/java/org/mozilla/gecko/JobIdsConstants.java
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
mobile/android/geckoview/src/main/AndroidManifest.xml
mobile/android/geckoview/src/main/java/org/mozilla/gecko/CrashHandler.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/CrashReporterService.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
toolkit/crashreporter/nsExceptionHandler.cpp
--- 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