[WebView Support Library] Add support for static WebView methods.

Add support for the support-library version of
WebViewFactoryProvider.Statics, used for implementing static WebView
methods.

Bug: 73151403
Test: run androidx.webkit tests (existing + newly added ones) on an L
device and a P device.
Change-Id: I8d161a67fba78b3793a5d7091358ed429da28487
diff --git a/webkit/api/current.txt b/webkit/api/current.txt
index 7a3fe25..2a39d42 100644
--- a/webkit/api/current.txt
+++ b/webkit/api/current.txt
@@ -10,7 +10,10 @@
   }
 
   public class WebViewCompat {
+    method public static android.net.Uri getSafeBrowsingPrivacyPolicyUrl();
     method public static void postVisualStateCallback(android.webkit.WebView, long, androidx.webkit.WebViewCompat.VisualStateCallback);
+    method public static void setSafeBrowsingWhitelist(java.util.List<java.lang.String>, android.webkit.ValueCallback<java.lang.Boolean>);
+    method public static void startSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>);
   }
 
   public static abstract interface WebViewCompat.VisualStateCallback {
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewCompatTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewCompatTest.java
index 8b38d99..a9ffead 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewCompatTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewCompatTest.java
@@ -17,20 +17,38 @@
 package androidx.webkit;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.Build;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.os.BuildCompat;
+import android.webkit.SafeBrowsingResponse;
+import android.webkit.ValueCallback;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
 
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+@MediumTest
 @RunWith(AndroidJUnit4.class)
 public class WebViewCompatTest {
     WebViewOnUiThread mWebViewOnUiThread;
@@ -42,7 +60,6 @@
         mWebViewOnUiThread = new androidx.webkit.WebViewOnUiThread();
     }
 
-    @MediumTest
     @Test
     public void testVisualStateCallbackCalled() throws Exception {
         // TODO(gsennton) activate this test for pre-P devices when we can pre-install a WebView APK
@@ -65,7 +82,6 @@
         assertTrue(callbackLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
     }
 
-    @MediumTest
     @Test
     public void testCheckThread() {
         try {
@@ -80,4 +96,142 @@
         }
         fail("Calling a WebViewCompat method on the wrong thread must cause a run-time exception");
     }
+
+    private static class MockContext extends ContextWrapper {
+        private boolean mGetApplicationContextWasCalled;
+
+        MockContext(Context context) {
+            super(context);
+        }
+
+        public Context getApplicationContext() {
+            mGetApplicationContextWasCalled = true;
+            return super.getApplicationContext();
+        }
+
+        public boolean wasGetApplicationContextCalled() {
+            return mGetApplicationContextWasCalled;
+        }
+    }
+
+    @Test
+    public void testStartSafeBrowsingUseApplicationContext() throws Exception {
+        // TODO(gsennton) activate this test for pre-P 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.O_MR1) return;
+
+        final MockContext ctx =
+                new MockContext(InstrumentationRegistry.getTargetContext().getApplicationContext());
+        final CountDownLatch resultLatch = new CountDownLatch(1);
+        WebViewCompat.startSafeBrowsing(ctx, new ValueCallback<Boolean>() {
+            @Override
+            public void onReceiveValue(Boolean value) {
+                assertTrue(ctx.wasGetApplicationContextCalled());
+                resultLatch.countDown();
+                return;
+            }
+        });
+        assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testStartSafeBrowsingWithNullCallbackDoesntCrash() throws Exception {
+        // TODO(gsennton) activate this test for pre-P 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.O_MR1) return;
+
+        WebViewCompat.startSafeBrowsing(InstrumentationRegistry.getTargetContext(), null);
+    }
+
+    @Test
+    public void testStartSafeBrowsingInvokesCallback() throws Exception {
+        // TODO(gsennton) activate this test for pre-P 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.O_MR1) return;
+
+        final CountDownLatch resultLatch = new CountDownLatch(1);
+        WebViewCompat.startSafeBrowsing(
+                InstrumentationRegistry.getTargetContext().getApplicationContext(),
+                new ValueCallback<Boolean>() {
+                    @Override
+                    public void onReceiveValue(Boolean value) {
+                        assertTrue(Looper.getMainLooper().isCurrentThread());
+                        resultLatch.countDown();
+                        return;
+                    }
+                });
+        assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testSetSafeBrowsingWhitelistWithMalformedList() throws Exception {
+        // TODO(gsennton) activate this test for pre-P 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.O_MR1) return;
+
+        List whitelist = new ArrayList<String>();
+        // Protocols are not supported in the whitelist
+        whitelist.add("http://google.com");
+        final CountDownLatch resultLatch = new CountDownLatch(1);
+        WebViewCompat.setSafeBrowsingWhitelist(whitelist, new ValueCallback<Boolean>() {
+            @Override
+            public void onReceiveValue(Boolean success) {
+                assertFalse(success);
+                resultLatch.countDown();
+            }
+        });
+        assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testSetSafeBrowsingWhitelistWithValidList() throws Exception {
+        // TODO(gsennton) activate this test for pre-P 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.O_MR1) return;
+
+        List whitelist = new ArrayList<String>();
+        whitelist.add("safe-browsing");
+        final CountDownLatch resultLatch = new CountDownLatch(1);
+        WebViewCompat.setSafeBrowsingWhitelist(whitelist, new ValueCallback<Boolean>() {
+            @Override
+            public void onReceiveValue(Boolean success) {
+                assertTrue(success);
+                resultLatch.countDown();
+            }
+        });
+        assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+
+        final CountDownLatch resultLatch2 = new CountDownLatch(1);
+        mWebViewOnUiThread.setWebViewClient(new WebViewClient() {
+            @Override
+            public void onPageFinished(WebView view, String url) {
+                resultLatch2.countDown();
+            }
+
+            @Override
+            public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType,
+                    SafeBrowsingResponse callback) {
+                Assert.fail("Should not invoke onSafeBrowsingHit");
+            }
+        });
+
+        mWebViewOnUiThread.loadUrl("chrome://safe-browsing/match?type=malware");
+
+        // Wait until page load has completed
+        assertTrue(resultLatch2.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testGetSafeBrowsingPrivacyPolicyUrl() throws Exception {
+        // TODO(gsennton) activate this test for pre-P 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.O_MR1) return;
+
+        assertNotNull(WebViewCompat.getSafeBrowsingPrivacyPolicyUrl());
+        try {
+            new URL(WebViewCompat.getSafeBrowsingPrivacyPolicyUrl().toString());
+        } catch (MalformedURLException e) {
+            Assert.fail("The privacy policy URL should be a well-formed URL");
+        }
+    }
 }
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java b/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
index a86775b..9b4c9e9 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
@@ -19,6 +19,7 @@
 import android.support.test.InstrumentationRegistry;
 import android.webkit.WebSettings;
 import android.webkit.WebView;
+import android.webkit.WebViewClient;
 
 public class WebViewOnUiThread {
     private WebView mWebView;
@@ -41,6 +42,15 @@
         });
     }
 
+    public void setWebViewClient(final WebViewClient webviewClient) {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mWebView.setWebViewClient(webviewClient);
+            }
+        });
+    }
+
     public void postVisualStateCallbackCompat(final long requestId,
             final WebViewCompat.VisualStateCallback callback) {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
diff --git a/webkit/src/main/java/androidx/webkit/WebViewCompat.java b/webkit/src/main/java/androidx/webkit/WebViewCompat.java
index 3141918..ee328aa 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewCompat.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewCompat.java
@@ -16,16 +16,21 @@
 
 package androidx.webkit;
 
+import android.content.Context;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Looper;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.os.BuildCompat;
+import android.webkit.ValueCallback;
 import android.webkit.WebView;
 
 import org.chromium.support_lib_boundary.WebViewProviderBoundaryInterface;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.List;
 
 import androidx.webkit.internal.WebViewGlueCommunicator;
 import androidx.webkit.internal.WebViewProviderAdapter;
@@ -120,6 +125,79 @@
         }
     }
 
+    /**
+     * Starts Safe Browsing initialization.
+     * <p>
+     * URL loads are not guaranteed to be protected by Safe Browsing until after {@code callback} is
+     * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those
+     * devices {@code callback} will receive {@code false}.
+     * <p>
+     * This should not be called if Safe Browsing has been disabled by manifest tag or {@link
+     * android.webkit.WebSettings#setSafeBrowsingEnabled}. This prepares resources used for Safe
+     * Browsing.
+     * <p>
+     * This should be called with the Application Context (and will always use the Application
+     * context to do its work regardless).
+     *
+     * @param context Application Context.
+     * @param callback will be called on the UI thread with {@code true} if initialization is
+     * successful, {@code false} otherwise.
+     */
+    public static void startSafeBrowsing(@NonNull Context context,
+            @Nullable ValueCallback<Boolean> callback) {
+        if (Build.VERSION.SDK_INT >= 27) {
+            WebView.startSafeBrowsing(context, callback);
+        } else { // TODO(gsennton): guard with WebViewApk.hasFeature(SafeBrowsing)
+            getFactory().getStatics().initSafeBrowsing(context, callback);
+        }
+    }
+
+    /**
+     * Sets the list of hosts (domain names/IP addresses) that are exempt from SafeBrowsing checks.
+     * The list is global for all the WebViews.
+     * <p>
+     * Each rule should take one of these:
+     * <table>
+     * <tr><th> Rule </th> <th> Example </th> <th> Matches Subdomain</th> </tr>
+     * <tr><td> HOSTNAME </td> <td> example.com </td> <td> Yes </td> </tr>
+     * <tr><td> .HOSTNAME </td> <td> .example.com </td> <td> No </td> </tr>
+     * <tr><td> IPV4_LITERAL </td> <td> 192.168.1.1 </td> <td> No </td></tr>
+     * <tr><td> IPV6_LITERAL_WITH_BRACKETS </td><td>[10:20:30:40:50:60:70:80]</td><td>No</td></tr>
+     * </table>
+     * <p>
+     * All other rules, including wildcards, are invalid.
+     * <p>
+     * The correct syntax for hosts is defined by <a
+     * href="https://tools.ietf.org/html/rfc3986#section-3.2.2">RFC 3986</a>.
+     *
+     * @param hosts the list of hosts
+     * @param callback will be called with {@code true} if hosts are successfully added to the
+     * whitelist. It will be called with {@code false} if any hosts are malformed. The callback
+     * will be run on the UI thread
+     */
+    public static void setSafeBrowsingWhitelist(@NonNull List<String> hosts,
+            @Nullable ValueCallback<Boolean> callback) {
+        if (Build.VERSION.SDK_INT >= 27) {
+            WebView.setSafeBrowsingWhitelist(hosts, callback);
+        } else { // TODO(gsennton): guard with WebViewApk.hasFeature(SafeBrowsing)
+            getFactory().getStatics().setSafeBrowsingWhitelist(hosts, callback);
+        }
+    }
+
+    /**
+     * Returns a URL pointing to the privacy policy for Safe Browsing reporting.
+     *
+     * @return the url pointing to a privacy policy document which can be displayed to users.
+     */
+    @NonNull
+    public static Uri getSafeBrowsingPrivacyPolicyUrl() {
+        if (Build.VERSION.SDK_INT >= 27) {
+            return WebView.getSafeBrowsingPrivacyPolicyUrl();
+        } else { // TODO(gsennton): guard with WebViewApk.hasFeature(SafeBrowsing)
+            return getFactory().getStatics().getSafeBrowsingPrivacyPolicyUrl();
+        }
+    }
+
     private static WebViewProviderAdapter getProvider(WebView webview) {
         return new WebViewProviderAdapter(createProvider(webview));
     }
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java b/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java
index fe98a56..80067ed 100644
--- a/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewProviderFactoryAdapter.java
@@ -19,6 +19,7 @@
 import android.webkit.WebView;
 
 import org.chromium.support_lib_boundary.BoundaryInterfaceReflectionUtil;
+import org.chromium.support_lib_boundary.StaticsBoundaryInterface;
 import org.chromium.support_lib_boundary.WebViewProviderBoundaryInterface;
 import org.chromium.support_lib_boundary.WebViewProviderFactoryBoundaryInterface;
 import org.chromium.support_lib_boundary.WebkitToCompatConverterBoundaryInterface;
@@ -53,4 +54,13 @@
         return BoundaryInterfaceReflectionUtil.castToSuppLibClass(
                 WebkitToCompatConverterBoundaryInterface.class, mImpl.getWebkitToCompatConverter());
     }
+
+    /**
+     * Adapter method for fetching the support library class representing
+     * {@link android.webkit.WebViewFactoryProvider#Statics}.
+     */
+    public StaticsBoundaryInterface getStatics() {
+        return BoundaryInterfaceReflectionUtil.castToSuppLibClass(
+                StaticsBoundaryInterface.class, mImpl.getStatics());
+    }
 }