WebView JavasScript channels Android implementation. (#1130)

Platform implementation of the method channel API for adding and removing JavaScript channels.

#1116 adds the Dart side support, the current PR will land first.

flutter/flutter#24837
diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
index 58f249a..a6528cd 100644
--- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
+++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
@@ -9,22 +9,31 @@
 import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
 import io.flutter.plugin.common.MethodChannel.Result;
 import io.flutter.plugin.platform.PlatformView;
+import java.util.List;
 import java.util.Map;
 
 public class FlutterWebView implements PlatformView, MethodCallHandler {
+  private static final String JS_CHANNEL_NAMES_FIELD = "javascriptChannelNames";
   private final WebView webView;
   private final MethodChannel methodChannel;
 
   @SuppressWarnings("unchecked")
   FlutterWebView(Context context, BinaryMessenger messenger, int id, Map<String, Object> params) {
     webView = new WebView(context);
+
+    methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id);
+    methodChannel.setMethodCallHandler(this);
+
+    applySettings((Map<String, Object>) params.get("settings"));
+
+    if (params.containsKey(JS_CHANNEL_NAMES_FIELD)) {
+      registerJavaScriptChannelNames((List<String>) params.get(JS_CHANNEL_NAMES_FIELD));
+    }
+
     if (params.containsKey("initialUrl")) {
       String url = (String) params.get("initialUrl");
       webView.loadUrl(url);
     }
-    applySettings((Map<String, Object>) params.get("settings"));
-    methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id);
-    methodChannel.setMethodCallHandler(this);
   }
 
   @Override
@@ -62,6 +71,12 @@
       case "evaluateJavascript":
         evaluateJavaScript(methodCall, result);
         break;
+      case "addJavascriptChannels":
+        addJavaScriptChannels(methodCall, result);
+        break;
+      case "removeJavascriptChannels":
+        removeJavaScriptChannels(methodCall, result);
+        break;
       default:
         result.notImplemented();
     }
@@ -125,6 +140,22 @@
         });
   }
 
+  @SuppressWarnings("unchecked")
+  private void addJavaScriptChannels(MethodCall methodCall, Result result) {
+    List<String> channelNames = (List<String>) methodCall.arguments;
+    registerJavaScriptChannelNames(channelNames);
+    result.success(null);
+  }
+
+  @SuppressWarnings("unchecked")
+  private void removeJavaScriptChannels(MethodCall methodCall, Result result) {
+    List<String> channelNames = (List<String>) methodCall.arguments;
+    for (String channelName : channelNames) {
+      webView.removeJavascriptInterface(channelName);
+    }
+    result.success(null);
+  }
+
   private void applySettings(Map<String, Object> settings) {
     for (String key : settings.keySet()) {
       switch (key) {
@@ -150,6 +181,13 @@
     }
   }
 
+  private void registerJavaScriptChannelNames(List<String> channelNames) {
+    for (String channelName : channelNames) {
+      webView.addJavascriptInterface(
+          new JavaScriptChannel(methodChannel, channelName), channelName);
+    }
+  }
+
   @Override
   public void dispose() {}
 }
diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java
new file mode 100644
index 0000000..1fba623
--- /dev/null
+++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java
@@ -0,0 +1,38 @@
+package io.flutter.plugins.webviewflutter;
+
+import android.webkit.JavascriptInterface;
+import io.flutter.plugin.common.MethodChannel;
+import java.util.HashMap;
+
+/**
+ * Added as a JavaScript interface to the WebView for any JavaScript channel that the Dart code sets
+ * up.
+ *
+ * <p>Exposes a single method named `postMessage` to JavaScript, which sends a message over a method
+ * channel to the Dart code.
+ */
+class JavaScriptChannel {
+  private final MethodChannel methodChannel;
+  private final String javaScriptChannelName;
+
+  /**
+   * @param methodChannel the Flutter WebView method channel to which JS messages are sent
+   * @param javaScriptChannelName the name of the JavaScript channel, this is sent over the method
+   *     channel with each message to let the Dart code know which JavaScript channel the message
+   *     was sent through
+   */
+  JavaScriptChannel(MethodChannel methodChannel, String javaScriptChannelName) {
+    this.methodChannel = methodChannel;
+    this.javaScriptChannelName = javaScriptChannelName;
+  }
+
+  // Suppressing unused warning as this is invoked from JavaScript.
+  @SuppressWarnings("unused")
+  @JavascriptInterface
+  public void postMessage(String message) {
+    HashMap<String, String> arguments = new HashMap<>();
+    arguments.put("channel", javaScriptChannelName);
+    arguments.put("message", message);
+    methodChannel.invokeMethod("javascriptChannelMessage", arguments);
+  }
+}