[WebView Support Library] Add framework-ServiceWorker support.

Add WebView Support Library support for ServiceWorkers - using only the
framework android.webkit APIs for now. Hooking into the WebView APK code
directly will happen in a follow-up to avoid massive CLs.

Also include a test for ServiceWorkerClientCompat. This test
corresponds to the CTS test for ServiceWorkerClient.

We'll add feature flags for the ServiceWorker APIs in a follow-up.

Bug: 73151166
Test: run androidx.webkit tests.

Change-Id: Ie3042c0a95987e2864ec57c7cff0c30d4d974fd8
diff --git a/webkit/api/current.txt b/webkit/api/current.txt
index 4dba759..36bc76b 100644
--- a/webkit/api/current.txt
+++ b/webkit/api/current.txt
@@ -1,5 +1,27 @@
 package androidx.webkit {
 
+  public abstract class ServiceWorkerClientCompat {
+    ctor public ServiceWorkerClientCompat();
+    method public abstract android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebResourceRequest);
+  }
+
+  public abstract class ServiceWorkerControllerCompat {
+    method public static androidx.webkit.ServiceWorkerControllerCompat getInstance();
+    method public abstract androidx.webkit.ServiceWorkerWebSettingsCompat getServiceWorkerWebSettings();
+    method public abstract void setServiceWorkerClient(androidx.webkit.ServiceWorkerClientCompat);
+  }
+
+  public abstract class ServiceWorkerWebSettingsCompat {
+    method public abstract boolean getAllowContentAccess();
+    method public abstract boolean getAllowFileAccess();
+    method public abstract boolean getBlockNetworkLoads();
+    method public abstract int getCacheMode();
+    method public abstract void setAllowContentAccess(boolean);
+    method public abstract void setAllowFileAccess(boolean);
+    method public abstract void setBlockNetworkLoads(boolean);
+    method public abstract void setCacheMode(int);
+  }
+
   public class WebSettingsCompat {
     method public static int getDisabledActionModeMenuItems(android.webkit.WebSettings);
     method public static boolean getOffscreenPreRaster(android.webkit.WebSettings);
diff --git a/webkit/src/androidTest/java/androidx/webkit/ServiceWorkerClientCompatTest.java b/webkit/src/androidTest/java/androidx/webkit/ServiceWorkerClientCompatTest.java
new file mode 100644
index 0000000..aecbc34
--- /dev/null
+++ b/webkit/src/androidTest/java/androidx/webkit/ServiceWorkerClientCompatTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Build;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ServiceWorkerClientCompatTest {
+
+    // The BASE_URL does not matter since the tests will intercept the load, but it should be https
+    // for the Service Worker registration to succeed.
+    private static final String BASE_URL = "https://www.example.com/";
+    private static final String INDEX_URL = BASE_URL + "index.html";
+    private static final String SW_URL = BASE_URL + "sw.js";
+    private static final String FETCH_URL = BASE_URL + "fetch.html";
+
+    private static final String JS_INTERFACE_NAME = "Android";
+    private static final int POLLING_TIMEOUT = 10 * 1000;
+
+    // static HTML page always injected instead of the url loaded.
+    private static final String INDEX_RAW_HTML =
+            "<!DOCTYPE html>\n"
+                    + "<html>\n"
+                    + "  <body>\n"
+                    + "    <script>\n"
+                    + "      navigator.serviceWorker.register('sw.js').then(function(reg) {\n"
+                    + "         " + JS_INTERFACE_NAME + ".registrationSuccess();\n"
+                    + "      }).catch(function(err) {\n"
+                    + "         console.error(err);\n"
+                    + "      });\n"
+                    + "    </script>\n"
+                    + "  </body>\n"
+                    + "</html>\n";
+    private static final String SW_RAW_HTML = "fetch('fetch.html');";
+    private static final String SW_UNREGISTER_RAW_JS =
+            "navigator.serviceWorker.getRegistration().then(function(r) {"
+                    + "  r.unregister().then(function(success) {"
+                    + "    if (success) " + JS_INTERFACE_NAME + ".unregisterSuccess();"
+                    + "    else console.error('unregister() was not successful');"
+                    + "  });"
+                    + "}).catch(function(err) {"
+                    + "   console.error(err);"
+                    + "});";
+
+    private JavascriptStatusReceiver mJavascriptStatusReceiver;
+    private WebViewOnUiThread mOnUiThread;
+
+    // Both this test and WebViewOnUiThread need to override some of the methods on WebViewClient,
+    // so this test subclasses the WebViewClient from WebViewOnUiThread.
+    private static class InterceptClient extends WebViewOnUiThread.WaitForLoadedClient {
+
+        InterceptClient(WebViewOnUiThread webViewOnUiThread) throws Exception {
+            super(webViewOnUiThread);
+        }
+
+        @Override
+        public WebResourceResponse shouldInterceptRequest(WebView view,
+                WebResourceRequest request) {
+            // Only return content for INDEX_URL, deny all other requests.
+            try {
+                if (request.getUrl().toString().equals(INDEX_URL)) {
+                    return new WebResourceResponse("text/html", "utf-8",
+                            new ByteArrayInputStream(INDEX_RAW_HTML.getBytes("UTF-8")));
+                }
+            } catch (java.io.UnsupportedEncodingException e) { }
+            return new WebResourceResponse("text/html", "UTF-8", null);
+        }
+    }
+
+    public static class InterceptServiceWorkerClient extends ServiceWorkerClientCompat {
+        private List<WebResourceRequest> mInterceptedRequests = new ArrayList<WebResourceRequest>();
+
+        @Override
+        public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
+            // Records intercepted requests and only return content for SW_URL.
+            mInterceptedRequests.add(request);
+            try {
+                if (request.getUrl().toString().equals(SW_URL)) {
+                    return new WebResourceResponse("application/javascript", "utf-8",
+                            new ByteArrayInputStream(SW_RAW_HTML.getBytes("UTF-8")));
+                }
+            } catch (java.io.UnsupportedEncodingException e) { }
+            return new WebResourceResponse("text/html", "UTF-8", null);
+        }
+
+        List<WebResourceRequest> getInterceptedRequests() {
+            return mInterceptedRequests;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mOnUiThread = new WebViewOnUiThread();
+        mOnUiThread.getSettings().setJavaScriptEnabled(true);
+
+        mJavascriptStatusReceiver = new JavascriptStatusReceiver();
+        mOnUiThread.addJavascriptInterface(mJavascriptStatusReceiver, JS_INTERFACE_NAME);
+        mOnUiThread.setWebViewClient(new InterceptClient(mOnUiThread));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mOnUiThread != null) {
+            mOnUiThread.cleanUp();
+        }
+    }
+
+    // Test correct invocation of shouldInterceptRequest for Service Workers.
+    @Test
+    public void testServiceWorkerClientInterceptCallback() throws Exception {
+        // TODO(gsennton) activate this test for pre-N devices when we can pre-install a WebView APK
+        // containing support for the WebView Support Library, see b/73454652.
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return;
+
+        final InterceptServiceWorkerClient mInterceptServiceWorkerClient =
+                new InterceptServiceWorkerClient();
+        ServiceWorkerControllerCompat swController = ServiceWorkerControllerCompat.getInstance();
+        swController.setServiceWorkerClient(mInterceptServiceWorkerClient);
+
+        mOnUiThread.loadUrlAndWaitForCompletion(INDEX_URL);
+
+        Callable<Boolean> registrationSuccess = new Callable<Boolean>() {
+            @Override
+            public Boolean call() {
+                return mJavascriptStatusReceiver.mRegistrationSuccess;
+            }
+        };
+        PollingCheck.check("JS could not register Service Worker", POLLING_TIMEOUT,
+                registrationSuccess);
+
+        Callable<Boolean> receivedRequest = new Callable<Boolean>() {
+            @Override
+            public Boolean call() {
+                return mInterceptServiceWorkerClient.getInterceptedRequests().size() >= 2;
+            }
+        };
+        PollingCheck.check("Service Worker intercept callbacks not invoked", POLLING_TIMEOUT,
+                receivedRequest);
+
+        List<WebResourceRequest> requests = mInterceptServiceWorkerClient.getInterceptedRequests();
+        assertEquals(2, requests.size());
+        assertEquals(SW_URL, requests.get(0).getUrl().toString());
+        assertEquals(FETCH_URL, requests.get(1).getUrl().toString());
+
+        // Clean-up, make sure to unregister the Service Worker.
+        mOnUiThread.evaluateJavascript(SW_UNREGISTER_RAW_JS, null);
+        Callable<Boolean> unregisterSuccess = new Callable<Boolean>() {
+            @Override
+            public Boolean call() {
+                return mJavascriptStatusReceiver.mUnregisterSuccess;
+            }
+        };
+        PollingCheck.check("JS could not unregister Service Worker", POLLING_TIMEOUT,
+                unregisterSuccess);
+    }
+
+    // Object added to the page via AddJavascriptInterface() that is used by the test Javascript to
+    // notify back to Java if the Service Worker registration was successful.
+    public static final class JavascriptStatusReceiver {
+        public volatile boolean mRegistrationSuccess = false;
+        public volatile boolean mUnregisterSuccess = false;
+
+        @JavascriptInterface
+        public void registrationSuccess() {
+            mRegistrationSuccess = true;
+        }
+
+        @JavascriptInterface
+        public void unregisterSuccess() {
+            mUnregisterSuccess = true;
+        }
+    }
+}
diff --git a/webkit/src/main/java/androidx/webkit/ServiceWorkerClientCompat.java b/webkit/src/main/java/androidx/webkit/ServiceWorkerClientCompat.java
new file mode 100644
index 0000000..19aab8c
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/ServiceWorkerClientCompat.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit;
+
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Base class for clients to capture Service Worker related callbacks,
+ * see {@link ServiceWorkerControllerCompat} for usage example.
+ */
+public abstract class ServiceWorkerClientCompat {
+    /**
+     *
+     * Notify the host application of a resource request and allow the
+     * application to return the data. If the return value is {@code null}, the
+     * Service Worker will continue to load the resource as usual.
+     * Otherwise, the return response and data will be used.
+     *
+     * <p class="note"><b>Note:</b> This method is called on a thread other than the UI thread so
+     * clients should exercise caution when accessing private data or the view system.
+     *
+     * @param request Object containing the details of the request.
+     * @return A {@link android.webkit.WebResourceResponse} containing the
+     * response information or {@code null} if the WebView should load the
+     * resource itself.
+     * @see android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView,
+     * WebResourceRequest)
+     *
+     */
+    public abstract WebResourceResponse shouldInterceptRequest(@NonNull WebResourceRequest request);
+}
diff --git a/webkit/src/main/java/androidx/webkit/ServiceWorkerControllerCompat.java b/webkit/src/main/java/androidx/webkit/ServiceWorkerControllerCompat.java
new file mode 100644
index 0000000..0a27cd0
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/ServiceWorkerControllerCompat.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit;
+
+import android.os.Build;
+import android.webkit.ServiceWorkerController;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.webkit.internal.FrameworkServiceWorkerController;
+
+// TODO(gsennton) guard APIs with isFeatureSupported(String)
+
+/**
+ * Manages Service Workers used by WebView.
+ *
+ * <p>Example usage:
+ * <pre class="prettyprint">
+ * ServiceWorkerControllerCompat swController = ServiceWorkerControllerCompat.getInstance();
+ * swController.setServiceWorkerClient(new ServiceWorkerClientCompat() {
+ *   {@literal @}Override
+ *   public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
+ *     // Capture request here and generate response or allow pass-through
+ *     // by returning null.
+ *     return null;
+ *   }
+ * });
+ * </pre>
+ */
+public abstract class ServiceWorkerControllerCompat {
+    /**
+     *
+     * @hide Don't allow apps to sub-class this class.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public ServiceWorkerControllerCompat() {}
+
+    /**
+     * Returns the default ServiceWorkerController instance. At present there is
+     * only one ServiceWorkerController instance for all WebView instances,
+     * however this restriction may be relaxed in the future.
+     *
+     * @return the default ServiceWorkerController instance
+     */
+    @NonNull
+    public static ServiceWorkerControllerCompat getInstance() {
+        return LAZY_HOLDER.INSTANCE;
+    }
+
+    private static class LAZY_HOLDER {
+        static final ServiceWorkerControllerCompat INSTANCE =
+                Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+                        ? getFrameworkControllerCompat() : getSupportLibraryControllerCompat();
+    }
+
+    /**
+     * Return a version of {@link ServiceWorkerControllerCompat} that only uses framework APIs.
+     */
+    @RequiresApi(Build.VERSION_CODES.N)
+    private static ServiceWorkerControllerCompat getFrameworkControllerCompat() {
+        return new FrameworkServiceWorkerController(
+                ServiceWorkerController.getInstance());
+    }
+
+    private static ServiceWorkerControllerCompat getSupportLibraryControllerCompat() {
+        return null; // TODO(gsennton) implement this
+    }
+
+    /**
+     *
+     * Gets the settings for all service workers.
+     *
+     * @return the current {@link ServiceWorkerWebSettingsCompat}
+     *
+     */
+    @NonNull
+    public abstract ServiceWorkerWebSettingsCompat getServiceWorkerWebSettings();
+
+    /**
+     *
+     * Sets the client to capture service worker related callbacks.
+     *
+     * A {@link ServiceWorkerClientCompat} should be set before any service workers are
+     * active, e.g. a safe place is before any WebView instances are created or
+     * pages loaded.
+     *
+     */
+    public abstract void setServiceWorkerClient(@Nullable ServiceWorkerClientCompat client);
+}
diff --git a/webkit/src/main/java/androidx/webkit/ServiceWorkerWebSettingsCompat.java b/webkit/src/main/java/androidx/webkit/ServiceWorkerWebSettingsCompat.java
new file mode 100644
index 0000000..61c46c3
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/ServiceWorkerWebSettingsCompat.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit;
+
+import android.webkit.WebSettings;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Manages settings state for all Service Workers. These settings are not tied to
+ * the lifetime of any WebView because service workers can outlive WebView instances.
+ * The settings are similar to {@link WebSettings} but only settings relevant to
+ * Service Workers are supported.
+ */
+public abstract class ServiceWorkerWebSettingsCompat {
+    /**
+     * @hide Don't allow apps to sub-class this class.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public ServiceWorkerWebSettingsCompat() {}
+
+    /** @hide */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @IntDef(value = {
+            WebSettings.LOAD_DEFAULT,
+            WebSettings.LOAD_CACHE_ELSE_NETWORK,
+            WebSettings.LOAD_NO_CACHE,
+            WebSettings.LOAD_CACHE_ONLY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CacheMode {}
+
+    /**
+     *
+     * Overrides the way the cache is used, see {@link WebSettings#setCacheMode}.
+     *
+     * @param mode the mode to use. One of {@link WebSettings#LOAD_DEFAULT},
+     * {@link WebSettings#LOAD_CACHE_ELSE_NETWORK}, {@link WebSettings#LOAD_NO_CACHE}
+     * or {@link WebSettings#LOAD_CACHE_ONLY}. The default value is
+     * {@link WebSettings#LOAD_DEFAULT}.
+     *
+     */
+    public abstract void setCacheMode(@CacheMode int mode);
+
+    /**
+     *
+     * Gets the current setting for overriding the cache mode.
+     *
+     * @return the current setting for overriding the cache mode
+     * @see #setCacheMode
+     *
+     */
+    public abstract @CacheMode int getCacheMode();
+
+    /**
+     *
+     * Enables or disables content URL access from Service Workers, see
+     * {@link WebSettings#setAllowContentAccess}.
+     *
+     */
+    public abstract void setAllowContentAccess(boolean allow);
+
+    /**
+     *
+     * Gets whether Service Workers support content URL access.
+     *
+     * @see #setAllowContentAccess
+     *
+     */
+    public abstract boolean getAllowContentAccess();
+
+    /**
+     *
+     * Enables or disables file access within Service Workers, see
+     * {@link WebSettings#setAllowFileAccess}.
+     *
+     */
+    public abstract void setAllowFileAccess(boolean allow);
+
+    /**
+     *
+     * Gets whether Service Workers support file access.
+     *
+     * @see #setAllowFileAccess
+     *
+     */
+    public abstract boolean getAllowFileAccess();
+
+    /**
+     *
+     * Sets whether Service Workers should not load resources from the network,
+     * see {@link WebSettings#setBlockNetworkLoads}.
+     *
+     * @param flag {@code true} means block network loads by the Service Workers
+     *
+     */
+    public abstract void setBlockNetworkLoads(boolean flag);
+
+    /**
+     *
+     * Gets whether Service Workers are prohibited from loading any resources from the network.
+     *
+     * @return {@code true} if the Service Workers are not allowed to load any resources from the
+     * network
+     * @see #setBlockNetworkLoads
+     *
+     */
+    public abstract boolean getBlockNetworkLoads();
+}
diff --git a/webkit/src/main/java/androidx/webkit/internal/FrameworkServiceWorkerClient.java b/webkit/src/main/java/androidx/webkit/internal/FrameworkServiceWorkerClient.java
new file mode 100644
index 0000000..c28346e
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/internal/FrameworkServiceWorkerClient.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit.internal;
+
+import android.os.Build;
+import android.webkit.ServiceWorkerClient;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+
+import androidx.annotation.RequiresApi;
+import androidx.webkit.ServiceWorkerClientCompat;
+
+/**
+ * A shim class that implements {@link ServiceWorkerClient} by delegating to a
+ * {@link ServiceWorkerClientCompat}.
+ * This class is used on up-to-date devices to avoid using reflection to call into WebView APK code.
+ */
+@RequiresApi(Build.VERSION_CODES.N)
+public class FrameworkServiceWorkerClient extends ServiceWorkerClient {
+    private final ServiceWorkerClientCompat mImpl;
+
+    public FrameworkServiceWorkerClient(ServiceWorkerClientCompat impl) {
+        mImpl = impl;
+    }
+
+    @Override
+    public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
+        return mImpl.shouldInterceptRequest(request);
+    }
+}
diff --git a/webkit/src/main/java/androidx/webkit/internal/FrameworkServiceWorkerController.java b/webkit/src/main/java/androidx/webkit/internal/FrameworkServiceWorkerController.java
new file mode 100644
index 0000000..2e02777
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/internal/FrameworkServiceWorkerController.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit.internal;
+
+import android.os.Build;
+import android.webkit.ServiceWorkerController;
+
+import androidx.annotation.RequiresApi;
+import androidx.webkit.ServiceWorkerClientCompat;
+import androidx.webkit.ServiceWorkerControllerCompat;
+import androidx.webkit.ServiceWorkerWebSettingsCompat;
+
+/**
+ * Implementation of {@link ServiceWorkerControllerCompat} meant for use on up-to-date platforms.
+ * This class does not use reflection to bypass framework APIs - instead it uses android.webkit
+ * APIs.
+ */
+@RequiresApi(Build.VERSION_CODES.N)
+public class FrameworkServiceWorkerController extends ServiceWorkerControllerCompat {
+    private final ServiceWorkerController mImpl;
+    private ServiceWorkerWebSettingsCompat mSettings;
+
+    public FrameworkServiceWorkerController(ServiceWorkerController impl) {
+        mImpl = impl;
+    }
+
+    @Override
+    public ServiceWorkerWebSettingsCompat getServiceWorkerWebSettings() {
+        if (mSettings == null) {
+            mSettings = new FrameworksServiceWorkerWebSettings(mImpl.getServiceWorkerWebSettings());
+        }
+        return mSettings;
+    }
+
+    @Override
+    public void setServiceWorkerClient(ServiceWorkerClientCompat client) {
+        mImpl.setServiceWorkerClient(new FrameworkServiceWorkerClient(client));
+    }
+}
diff --git a/webkit/src/main/java/androidx/webkit/internal/FrameworksServiceWorkerWebSettings.java b/webkit/src/main/java/androidx/webkit/internal/FrameworksServiceWorkerWebSettings.java
new file mode 100644
index 0000000..4373756
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/internal/FrameworksServiceWorkerWebSettings.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit.internal;
+
+import android.os.Build;
+import android.webkit.ServiceWorkerWebSettings;
+
+import androidx.annotation.RequiresApi;
+import androidx.webkit.ServiceWorkerWebSettingsCompat;
+
+/**
+ * Implementation of {@link ServiceWorkerWebSettingsCompat} meant for use on up-to-date platforms.
+ * This class does not use reflection to bypass framework APIs - instead it uses android.webkit
+ * APIs.
+ */
+@RequiresApi(Build.VERSION_CODES.N)
+public class FrameworksServiceWorkerWebSettings extends ServiceWorkerWebSettingsCompat {
+    private final ServiceWorkerWebSettings mImpl;
+
+    public FrameworksServiceWorkerWebSettings(ServiceWorkerWebSettings impl) {
+        mImpl = impl;
+    }
+
+    @Override
+    public void setCacheMode(int mode) {
+        mImpl.setCacheMode(mode);
+    }
+
+    @Override
+    public int getCacheMode() {
+        return mImpl.getCacheMode();
+    }
+
+    @Override
+    public void setAllowContentAccess(boolean allow) {
+        mImpl.setAllowContentAccess(allow);
+    }
+
+    @Override
+    public boolean getAllowContentAccess() {
+        return mImpl.getAllowContentAccess();
+    }
+
+    @Override
+    public void setAllowFileAccess(boolean allow) {
+        mImpl.setAllowContentAccess(allow);
+    }
+
+    @Override
+    public boolean getAllowFileAccess() {
+        return mImpl.getAllowFileAccess();
+    }
+
+    @Override
+    public void setBlockNetworkLoads(boolean flag) {
+        mImpl.setAllowContentAccess(flag);
+    }
+
+    @Override
+    public boolean getBlockNetworkLoads() {
+        return mImpl.getBlockNetworkLoads();
+    }
+}