Merge "[WebView Support Library] Support incompatible WebView APKs." into pi-androidx-dev
diff --git a/webkit/src/androidTest/java/androidx/webkit/IncompatibilityTest.java b/webkit/src/androidTest/java/androidx/webkit/IncompatibilityTest.java
new file mode 100644
index 0000000..09424c6
--- /dev/null
+++ b/webkit/src/androidTest/java/androidx/webkit/IncompatibilityTest.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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.webkit.internal.WebViewFeatureInternal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests ensuring that Android versions/setups that are incompatible with the WebView Support
+ * Library are handled gracefully.
+ *
+ * Only L+ Android versions are compatible with the WebView Support Library, so any tests in this
+ * class that guarantee certain behaviour for incompatible Android versions will only be run on
+ * pre-L devices.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class IncompatibilityTest {
+    @Test
+    @SdkSuppress(maxSdkVersion = 20)
+    public void testPreLDeviceHasNoWebViewFeatures() {
+        assertEquals(0, WebViewFeatureInternal.getWebViewApkFeaturesForTesting().length);
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 20)
+    public void testPreLDeviceDoesNotSupportVisualStateCallback() {
+        assertFalse(WebViewFeature.isFeatureSupported(WebViewFeature.VISUAL_STATE_CALLBACK));
+    }
+}
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewCompatTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewCompatTest.java
index bd77fdb..893b6df 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewCompatTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewCompatTest.java
@@ -29,7 +29,6 @@
 import android.os.Looper;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
-import android.support.test.filters.Suppress;
 import android.support.test.runner.AndroidJUnit4;
 import android.webkit.SafeBrowsingResponse;
 import android.webkit.ValueCallback;
@@ -85,9 +84,12 @@
         assertTrue(callbackLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
     }
 
-    @Suppress // TODO(gsennton) remove @Suppress when b/76202025 has been resolved
     @Test
     public void testCheckThread() {
+        if (!WebViewFeature.isFeatureSupported(WebViewFeature.VISUAL_STATE_CALLBACK)) {
+            // Skip this test if VisualStateCallback is not supported.
+            return;
+        }
         try {
             WebViewCompat.postVisualStateCallback(mWebViewOnUiThread.getWebViewOnCurrentThread(), 5,
                     new WebViewCompat.VisualStateCallback() {
diff --git a/webkit/src/main/java/androidx/webkit/WebViewCompat.java b/webkit/src/main/java/androidx/webkit/WebViewCompat.java
index 88de335..7cb0bfd 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewCompat.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewCompat.java
@@ -32,7 +32,7 @@
 import androidx.webkit.internal.WebViewFeatureInternal;
 import androidx.webkit.internal.WebViewGlueCommunicator;
 import androidx.webkit.internal.WebViewProviderAdapter;
-import androidx.webkit.internal.WebViewProviderFactoryAdapter;
+import androidx.webkit.internal.WebViewProviderFactory;
 
 import org.chromium.support_lib_boundary.WebViewProviderBoundaryInterface;
 
@@ -307,7 +307,7 @@
         return new WebViewProviderAdapter(createProvider(webview));
     }
 
-    private static WebViewProviderFactoryAdapter getFactory() {
+    private static WebViewProviderFactory getFactory() {
         return WebViewGlueCommunicator.getFactory();
     }
 
diff --git a/webkit/src/main/java/androidx/webkit/internal/IncompatibleApkWebViewProviderFactory.java b/webkit/src/main/java/androidx/webkit/internal/IncompatibleApkWebViewProviderFactory.java
new file mode 100644
index 0000000..71d5768
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/internal/IncompatibleApkWebViewProviderFactory.java
@@ -0,0 +1,62 @@
+/*
+ * 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.webkit.WebView;
+
+import org.chromium.support_lib_boundary.ServiceWorkerControllerBoundaryInterface;
+import org.chromium.support_lib_boundary.StaticsBoundaryInterface;
+import org.chromium.support_lib_boundary.WebViewProviderBoundaryInterface;
+import org.chromium.support_lib_boundary.WebkitToCompatConverterBoundaryInterface;
+
+/**
+ * This is a stub class used when the WebView Support Library is invoked on a device incompatible
+ * with the library (either a pre-L device or a device without a compatible WebView APK).
+ * The only method in this class that should be called is {@link #getWebViewFeatures()}.
+ */
+public class IncompatibleApkWebViewProviderFactory implements WebViewProviderFactory {
+    private static final String[] EMPTY_STRING_ARRAY = new String[0];
+    private static final String UNSUPPORTED_EXCEPTION_EXPLANATION =
+            "This should never happen, if this method was called it means we're trying to reach "
+            + "into WebView APK code on an incompatible device. This most likely means the current "
+            + "method is being called too early, or is being called on start-up rather than lazily";
+
+    @Override
+    public WebViewProviderBoundaryInterface createWebView(WebView webview) {
+        throw new UnsupportedOperationException(UNSUPPORTED_EXCEPTION_EXPLANATION);
+    }
+
+    @Override
+    public WebkitToCompatConverterBoundaryInterface getWebkitToCompatConverter() {
+        throw new UnsupportedOperationException(UNSUPPORTED_EXCEPTION_EXPLANATION);
+    }
+
+    @Override
+    public StaticsBoundaryInterface getStatics() {
+        throw new UnsupportedOperationException(UNSUPPORTED_EXCEPTION_EXPLANATION);
+    }
+
+    @Override
+    public String[] getWebViewFeatures() {
+        return EMPTY_STRING_ARRAY;
+    }
+
+    @Override
+    public ServiceWorkerControllerBoundaryInterface getServiceWorkerController() {
+        throw new UnsupportedOperationException(UNSUPPORTED_EXCEPTION_EXPLANATION);
+    }
+}
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java b/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
index d16713c..74f67e4 100644
--- a/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
@@ -77,6 +77,11 @@
                 WebViewGlueCommunicator.getFactory().getWebViewFeatures();
     }
 
+
+    public static String[] getWebViewApkFeaturesForTesting() {
+        return LAZY_HOLDER.WEBVIEW_APK_FEATURES;
+    }
+
     /**
      * Utility method for throwing an exception explaining that the feature the app trying to use
      * isn't supported.
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewGlueCommunicator.java b/webkit/src/main/java/androidx/webkit/internal/WebViewGlueCommunicator.java
index 33ac145..67e0030 100644
--- a/webkit/src/main/java/androidx/webkit/internal/WebViewGlueCommunicator.java
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewGlueCommunicator.java
@@ -39,25 +39,26 @@
     /**
      * Fetch the one global support library WebViewProviderFactory from the WebView glue layer.
      */
-    public static WebViewProviderFactoryAdapter getFactory() {
+    public static WebViewProviderFactory getFactory() {
         return LAZY_FACTORY_HOLDER.INSTANCE;
     }
 
     public static WebkitToCompatConverter getCompatConverter() {
-        return LAZY_FACTORY_HOLDER.COMPAT_CONVERTER;
+        return LAZY_COMPAT_CONVERTER_HOLDER.INSTANCE;
     }
 
     private static class LAZY_FACTORY_HOLDER {
-        static final WebViewProviderFactoryAdapter INSTANCE =
-                new WebViewProviderFactoryAdapter(
-                        WebViewGlueCommunicator.createGlueProviderFactory());
-        static final WebkitToCompatConverter COMPAT_CONVERTER =
-                new WebkitToCompatConverter(
-                        INSTANCE.getWebkitToCompatConverter());
+        private static final WebViewProviderFactory INSTANCE =
+                        WebViewGlueCommunicator.createGlueProviderFactory();
     }
 
-    private static InvocationHandler fetchGlueProviderFactoryImpl() {
-        try {
+    private static class LAZY_COMPAT_CONVERTER_HOLDER {
+        static final WebkitToCompatConverter INSTANCE = new WebkitToCompatConverter(
+                WebViewGlueCommunicator.getFactory().getWebkitToCompatConverter());
+    }
+
+    private static InvocationHandler fetchGlueProviderFactoryImpl() throws IllegalAccessException,
+            InvocationTargetException, ClassNotFoundException, NoSuchMethodException {
             Class<?> glueFactoryProviderFetcherClass = Class.forName(
                     GLUE_FACTORY_PROVIDER_FETCHER_CLASS, false, getWebViewClassLoader());
             Method createProviderFactoryMethod = glueFactoryProviderFetcherClass.getDeclaredMethod(
@@ -65,23 +66,28 @@
             return (InvocationHandler) createProviderFactoryMethod.invoke(null,
                     BoundaryInterfaceReflectionUtil.createInvocationHandlerFor(
                             new SupportLibraryInfo()));
+    }
+
+    private static WebViewProviderFactory createGlueProviderFactory() {
+        InvocationHandler invocationHandler;
+        try {
+            invocationHandler = fetchGlueProviderFactoryImpl();
+            // The only way we should fail to fetch the provider-factory is if the class we are
+            // calling into doesn't exist - any other kind of failure is unexpected and should cause
+            // a run-time exception.
         } catch (IllegalAccessException e) {
             throw new RuntimeException(e);
         } catch (InvocationTargetException e) {
             throw new RuntimeException(e);
         } catch (ClassNotFoundException e) {
-            throw new RuntimeException(e);
+            // If WebView APK support library glue entry point doesn't exist then return a Provider
+            // factory that declares that there are no features available.
+            return new IncompatibleApkWebViewProviderFactory();
         } catch (NoSuchMethodException e) {
             throw new RuntimeException(e);
         }
-        // TODO(gsennton) if the above happens we should avoid throwing an exception! And probably
-        // declare that the list of features supported by the WebView APK is empty.
-    }
-
-    private static WebViewProviderFactoryBoundaryInterface createGlueProviderFactory() {
-        InvocationHandler invocationHandler = fetchGlueProviderFactoryImpl();
-        return BoundaryInterfaceReflectionUtil.castToSuppLibClass(
-                WebViewProviderFactoryBoundaryInterface.class, invocationHandler);
+        return new WebViewProviderFactoryAdapter(BoundaryInterfaceReflectionUtil.castToSuppLibClass(
+                WebViewProviderFactoryBoundaryInterface.class, invocationHandler));
     }
 
     /**
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactory.java b/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactory.java
new file mode 100644
index 0000000..5e4669a
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactory.java
@@ -0,0 +1,60 @@
+/*
+ * 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.webkit.WebView;
+
+import org.chromium.support_lib_boundary.ServiceWorkerControllerBoundaryInterface;
+import org.chromium.support_lib_boundary.StaticsBoundaryInterface;
+import org.chromium.support_lib_boundary.WebViewProviderBoundaryInterface;
+import org.chromium.support_lib_boundary.WebkitToCompatConverterBoundaryInterface;
+
+/**
+ * Interface representing {@link android.webkit.WebViewProviderFactory}.
+ * On device with a compatible WebView APK this interface is implemented by a class defined in the
+ * WebView APK itself.
+ * On devices without a compatible WebView APK this interface is implemented by a stub class
+ * {@link androidx.webkit.internal.IncompatibleWebViewProviderFactory}.
+ */
+public interface WebViewProviderFactory {
+    /**
+     * Create a support library version of {@link android.webkit.WebViewProvider}.
+     */
+    WebViewProviderBoundaryInterface createWebView(WebView webview);
+
+    /**
+     * Create the boundary interface for {@link androidx.webkit.internal.WebkitToCompatConverter}
+     * which converts android.webkit classes into their corresponding support library classes.
+     */
+    WebkitToCompatConverterBoundaryInterface getWebkitToCompatConverter();
+
+    /**
+     * Fetch the boundary interface representing
+     * {@link android.webkit.WebViewFactoryProvider#Statics}.
+     */
+    StaticsBoundaryInterface getStatics();
+
+    /**
+     * Fetch the features supported by the current WebView APK.
+     */
+    String[] getWebViewFeatures();
+
+    /**
+     * Fetch the boundary interface representing {@link android.webkit.ServiceWorkerController}.
+     */
+    ServiceWorkerControllerBoundaryInterface getServiceWorkerController();
+}
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java b/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java
index efe0e64..43e5eae 100644
--- a/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java
@@ -29,7 +29,7 @@
  * Adapter for WebViewProviderFactoryBoundaryInterface providing static WebView functionality
  * similar to that provided by {@link android.webkit.WebViewFactoryProvider}.
  */
-public class WebViewProviderFactoryAdapter {
+public class WebViewProviderFactoryAdapter implements WebViewProviderFactory {
     WebViewProviderFactoryBoundaryInterface mImpl;
 
     public WebViewProviderFactoryAdapter(WebViewProviderFactoryBoundaryInterface impl) {
@@ -41,6 +41,7 @@
      * {@link android.webkit.WebViewProvider} - the class used to implement
      * {@link androidx.webkit.WebViewCompat}.
      */
+    @Override
     public WebViewProviderBoundaryInterface createWebView(WebView webview) {
         return BoundaryInterfaceReflectionUtil.castToSuppLibClass(
                 WebViewProviderBoundaryInterface.class, mImpl.createWebView(webview));
@@ -51,6 +52,7 @@
      * {@link androidx.webkit.internal.WebkitToCompatConverter}, which converts android.webkit
      * classes into their corresponding support library classes.
      */
+    @Override
     public WebkitToCompatConverterBoundaryInterface getWebkitToCompatConverter() {
         return BoundaryInterfaceReflectionUtil.castToSuppLibClass(
                 WebkitToCompatConverterBoundaryInterface.class, mImpl.getWebkitToCompatConverter());
@@ -60,6 +62,7 @@
      * Adapter method for fetching the support library class representing
      * {@link android.webkit.WebViewFactoryProvider#Statics}.
      */
+    @Override
     public StaticsBoundaryInterface getStatics() {
         return BoundaryInterfaceReflectionUtil.castToSuppLibClass(
                 StaticsBoundaryInterface.class, mImpl.getStatics());
@@ -68,6 +71,7 @@
     /**
      * Adapter method for fetching the features supported by the current WebView APK.
      */
+    @Override
     public String[] getWebViewFeatures() {
         return mImpl.getSupportedFeatures();
     }
@@ -76,6 +80,7 @@
      * Adapter method for fetching the support library class representing
      * {@link android.webkit.ServiceWorkerController}.
      */
+    @Override
     public ServiceWorkerControllerBoundaryInterface getServiceWorkerController() {
         return BoundaryInterfaceReflectionUtil.castToSuppLibClass(
                 ServiceWorkerControllerBoundaryInterface.class, mImpl.getServiceWorkerController());