[WebView Support Library] Add feature detection support.

We will use feature detection in the WebView Support Library to ensure
different versions of the support library are compatible with different
versions of the WebView APK, and to ensure we're forwards compatible (so
if we ever remove a feature we can simply mark that as removed in our
feature detection mechanism).

Bug: 38302180
Test: run androidx.webkit tests.
Change-Id: I8381845078a72872ab187ed66d5ab7c2ddcd2f91
diff --git a/annotations/src/main/java/androidx/annotation/RequiresFeature.java b/annotations/src/main/java/androidx/annotation/RequiresFeature.java
new file mode 100644
index 0000000..57450f8
--- /dev/null
+++ b/annotations/src/main/java/androidx/annotation/RequiresFeature.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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated element requires one or more features. This is used to auto-generate
+ * documentation, and more importantly: to ensure correct usage in application code, where lint and
+ * Android Studio can check that calls marked with this annotation is surrounded by has-feature
+ * calls, referenced via the {@link RequiresFeature#enforcement()} attribute.
+ */
+@Retention(SOURCE)
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
+public @interface RequiresFeature {
+    /**
+     * The name of the feature that is required.
+     */
+    String name();
+
+    /**
+     * Defines the name of the method that should be called to check whether the feature is
+     * available, using the same signature format as javadoc.
+     * The feature checking method can have multiple parameters, but the feature name parameter must
+     * be of type String and must also be the first String-type parameter.
+     */
+    String enforcement();
+}
diff --git a/webkit/api/current.txt b/webkit/api/current.txt
index 2a39d42..4dba759 100644
--- a/webkit/api/current.txt
+++ b/webkit/api/current.txt
@@ -20,5 +20,10 @@
     method public abstract void onComplete(long);
   }
 
+  public class WebViewFeature {
+    method public static boolean isFeatureSupported(java.lang.String);
+    field public static final java.lang.String VISUAL_STATE_CALLBACK = "VISUAL_STATE_CALLBACK";
+  }
+
 }
 
diff --git a/webkit/src/main/java/androidx/webkit/WebViewCompat.java b/webkit/src/main/java/androidx/webkit/WebViewCompat.java
index 6a48e35..0098a82 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewCompat.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewCompat.java
@@ -25,7 +25,9 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresFeature;
 import androidx.core.os.BuildCompat;
+import androidx.webkit.internal.WebViewFeatureInternal;
 import androidx.webkit.internal.WebViewGlueCommunicator;
 import androidx.webkit.internal.WebViewProviderAdapter;
 import androidx.webkit.internal.WebViewProviderFactoryAdapter;
@@ -104,13 +106,22 @@
      * {@link android.webkit.WebSettings#setOffscreenPreRaster} for more details and do consider its
      * caveats.
      *
+     * This method should only be called if
+     * {@link WebViewFeature#isFeatureSupported(String)}
+     * returns true for {@link WebViewFeature#VISUAL_STATE_CALLBACK}.
+     *
      * @param requestId An id that will be returned in the callback to allow callers to match
      *                  requests with callbacks.
      * @param callback  The callback to be invoked.
      */
+    @SuppressWarnings("NewApi")
+    @RequiresFeature(name = WebViewFeature.VISUAL_STATE_CALLBACK,
+            enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
     public static void postVisualStateCallback(@NonNull WebView webview, long requestId,
             @NonNull final VisualStateCallback callback) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+        WebViewFeatureInternal webViewFeature =
+                WebViewFeatureInternal.getFeature(WebViewFeature.VISUAL_STATE_CALLBACK);
+        if (webViewFeature.isSupportedByFramework()) {
             webview.postVisualStateCallback(requestId,
                     new android.webkit.WebView.VisualStateCallback() {
                         @Override
@@ -118,10 +129,11 @@
                             callback.onComplete(l);
                         }
                     });
-        } else {
-            // TODO(gsennton): guard with if WebViewApk.hasFeature(POSTVISUALSTATECALLBACK)
+        } else if (webViewFeature.isSupportedByWebView()) {
             checkThread(webview);
             getProvider(webview).insertVisualStateCallback(requestId, callback);
+        } else {
+            WebViewFeatureInternal.throwUnsupportedOperationException("postVisualStateCallback");
         }
     }
 
diff --git a/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
new file mode 100644
index 0000000..d514477
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -0,0 +1,65 @@
+/*
+ * 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 androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.StringDef;
+import androidx.webkit.internal.WebViewFeatureInternal;
+
+import org.chromium.support_lib_boundary.util.Features;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Utility class for checking which WebView Support Library features are supported on the device.
+ */
+public class WebViewFeature {
+
+    private WebViewFeature() {}
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @StringDef(value = {
+            VISUAL_STATE_CALLBACK,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.PARAMETER, ElementType.METHOD})
+    public @interface WebViewSupportFeature {}
+
+    /**
+     * Feature for {@link #isFeatureSupported(String)}.
+     * This feature covers
+     * {@link androidx.webkit.WebViewCompat#postVisualStateCallback(android.webkit.WebView, long,
+     * WebViewCompat.VisualStateCallback)}.
+     */
+    public static final String VISUAL_STATE_CALLBACK = Features.VISUAL_STATE_CALLBACK;
+
+    /**
+     * Return whether a feature is supported at run-time. This depends on the Android version of the
+     * device and the WebView APK on the device.
+     */
+    public static boolean isFeatureSupported(@NonNull @WebViewSupportFeature String feature) {
+        WebViewFeatureInternal webviewFeature = WebViewFeatureInternal.getFeature(feature);
+        return webviewFeature.isSupportedByFramework() || webviewFeature.isSupportedByWebView();
+    }
+}
diff --git a/webkit/src/main/java/androidx/webkit/internal/SupportLibraryInfo.java b/webkit/src/main/java/androidx/webkit/internal/SupportLibraryInfo.java
new file mode 100644
index 0000000..da8a02c
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/internal/SupportLibraryInfo.java
@@ -0,0 +1,38 @@
+/*
+ * 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 org.chromium.support_lib_boundary.SupportLibraryInfoBoundaryInterface;
+import org.chromium.support_lib_boundary.util.Features;
+
+/**
+ * Contains information about the Android Support Library part of the WebView Support Library - this
+ * information is passed to the WebView APK code with the first WebView Support Library call.
+ */
+public class SupportLibraryInfo implements SupportLibraryInfoBoundaryInterface {
+    // Features supported by the support library itself (regardless of what the WebView APK
+    // supports).
+    private static final String[] SUPPORTED_FEATURES =
+            new String[] {
+                    Features.VISUAL_STATE_CALLBACK
+            };
+
+    @Override
+    public String[] getSupportedFeatures() {
+        return SUPPORTED_FEATURES;
+    }
+}
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java b/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
new file mode 100644
index 0000000..d16713c
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
@@ -0,0 +1,88 @@
+/*
+ * 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 androidx.webkit.WebViewFeature;
+import androidx.webkit.WebViewFeature.WebViewSupportFeature;
+
+/**
+ * Enum representing a WebView feature, this provides functionality for determining whether a
+ * feature is supported by the current framework and/or WebView APK.
+ */
+public enum WebViewFeatureInternal {
+    /**
+     * This feature covers
+     * {@link androidx.webkit.WebViewCompat#postVisualStateCallback(android.webkit.WebView, long,
+     * androidx.webkit.WebViewCompat.VisualStateCallback)}.
+     */
+    VISUAL_STATE_CALLBACK_FEATURE(WebViewFeature.VISUAL_STATE_CALLBACK, Build.VERSION_CODES.M);
+
+    private final String mFeatureValue;
+    private final int mOsVersion;
+
+    WebViewFeatureInternal(@WebViewSupportFeature String featureValue, int osVersion) {
+        mFeatureValue = featureValue;
+        mOsVersion = osVersion;
+    }
+
+    /**
+     * Return the {@link WebViewFeatureInternal} corresponding to {@param feature}.
+     */
+    public static WebViewFeatureInternal getFeature(@WebViewSupportFeature String feature) {
+        switch (feature) {
+            case WebViewFeature.VISUAL_STATE_CALLBACK:
+                return VISUAL_STATE_CALLBACK_FEATURE;
+            default:
+                throw new RuntimeException("Unknown feature " + feature);
+        }
+    }
+
+    /**
+     * Return whether this {@link WebViewFeature} is supported by the framework of the current
+     * device.
+     */
+    public boolean isSupportedByFramework() {
+        return Build.VERSION.SDK_INT >= mOsVersion;
+    }
+
+    /**
+     * Return whether this {@link WebViewFeature} is supported by the current WebView APK.
+     */
+    public boolean isSupportedByWebView() {
+        String[] webviewFeatures = LAZY_HOLDER.WEBVIEW_APK_FEATURES;
+        for (String webviewFeature : webviewFeatures) {
+            if (webviewFeature.equals(mFeatureValue)) return true;
+        }
+        return false;
+    }
+
+    private static class LAZY_HOLDER {
+        static final String[] WEBVIEW_APK_FEATURES =
+                WebViewGlueCommunicator.getFactory().getWebViewFeatures();
+    }
+
+    /**
+     * Utility method for throwing an exception explaining that the feature the app trying to use
+     * isn't supported.
+     */
+    public static void throwUnsupportedOperationException(String feature) {
+        throw new UnsupportedOperationException("Feature " + feature
+                + " is not supported by the current version of the framework and WebView APK");
+    }
+}
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewGlueCommunicator.java b/webkit/src/main/java/androidx/webkit/internal/WebViewGlueCommunicator.java
index 6546fcf..5bb0666 100644
--- a/webkit/src/main/java/androidx/webkit/internal/WebViewGlueCommunicator.java
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewGlueCommunicator.java
@@ -61,10 +61,14 @@
             Class<?> glueFactoryProviderFetcherClass = Class.forName(
                     GLUE_FACTORY_PROVIDER_FETCHER_CLASS, false, getWebViewClassLoader());
             Method createProviderFactoryMethod = glueFactoryProviderFetcherClass.getDeclaredMethod(
-                    GLUE_FACTORY_PROVIDER_FETCHER_METHOD);
-            return (InvocationHandler) createProviderFactoryMethod.invoke(null);
+                    GLUE_FACTORY_PROVIDER_FETCHER_METHOD, InvocationHandler.class);
+            return (InvocationHandler) createProviderFactoryMethod.invoke(null,
+                    BoundaryInterfaceReflectionUtil.createInvocationHandlerFor(
+                            new SupportLibraryInfo()));
         } catch (IllegalAccessException | InvocationTargetException | ClassNotFoundException
                 | NoSuchMethodException e) {
+            // TODO(gsennton) if this happens we should avoid throwing an exception! And probably
+            // declare that the list of features supported by the WebView APK is empty.
             throw new RuntimeException(e);
         }
     }
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java b/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java
index 62ce41e..544dc6e 100644
--- a/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java
@@ -63,4 +63,11 @@
         return BoundaryInterfaceReflectionUtil.castToSuppLibClass(
                 StaticsBoundaryInterface.class, mImpl.getStatics());
     }
+
+    /**
+     * Adapter method for fetching the features supported by the current WebView APK.
+     */
+    public String[] getWebViewFeatures() {
+        return mImpl.getSupportedFeatures();
+    }
 }