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());