[webview_flutter] Add download listener to Android webview (#4322)
diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md
index 361bfd2..1e1d5aa 100644
--- a/packages/webview_flutter/webview_flutter/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 2.0.13
+* Send URL of File to download to the NavigationDelegate on Android just like it is already done on iOS.
* Updated Android lint settings.
## 2.0.12
diff --git a/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterDownloadListener.java b/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterDownloadListener.java
new file mode 100644
index 0000000..cfad4e3
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterDownloadListener.java
@@ -0,0 +1,33 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.webviewflutter;
+
+import android.webkit.DownloadListener;
+import android.webkit.WebView;
+
+/** DownloadListener to notify the {@link FlutterWebViewClient} of download starts */
+public class FlutterDownloadListener implements DownloadListener {
+ private final FlutterWebViewClient webViewClient;
+ private WebView webView;
+
+ public FlutterDownloadListener(FlutterWebViewClient webViewClient) {
+ this.webViewClient = webViewClient;
+ }
+
+ /** Sets the {@link WebView} that the result of the navigation delegate will be send to. */
+ public void setWebView(WebView webView) {
+ this.webView = webView;
+ }
+
+ @Override
+ public void onDownloadStart(
+ String url,
+ String userAgent,
+ String contentDisposition,
+ String mimetype,
+ long contentLength) {
+ webViewClient.notifyDownload(webView, url);
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
index a3b681f..4651a5f 100644
--- a/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
+++ b/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java
@@ -11,12 +11,14 @@
import android.os.Handler;
import android.os.Message;
import android.view.View;
+import android.webkit.DownloadListener;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
@@ -94,18 +96,25 @@
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
displayListenerProxy.onPreWebViewInitialization(displayManager);
+ this.methodChannel = methodChannel;
+ this.methodChannel.setMethodCallHandler(this);
+
+ flutterWebViewClient = new FlutterWebViewClient(methodChannel);
+
+ FlutterDownloadListener flutterDownloadListener =
+ new FlutterDownloadListener(flutterWebViewClient);
webView =
createWebView(
- new WebViewBuilder(context, containerView), params, new FlutterWebChromeClient());
+ new WebViewBuilder(context, containerView),
+ params,
+ new FlutterWebChromeClient(),
+ flutterDownloadListener);
+ flutterDownloadListener.setWebView(webView);
displayListenerProxy.onPostWebViewInitialization(displayManager);
platformThreadHandler = new Handler(context.getMainLooper());
- this.methodChannel = methodChannel;
- this.methodChannel.setMethodCallHandler(this);
-
- flutterWebViewClient = new FlutterWebViewClient(methodChannel);
Map<String, Object> settings = (Map<String, Object>) params.get("settings");
if (settings != null) {
applySettings(settings);
@@ -156,7 +165,10 @@
*/
@VisibleForTesting
static WebView createWebView(
- WebViewBuilder webViewBuilder, Map<String, Object> params, WebChromeClient webChromeClient) {
+ WebViewBuilder webViewBuilder,
+ Map<String, Object> params,
+ WebChromeClient webChromeClient,
+ @Nullable DownloadListener downloadListener) {
boolean usesHybridComposition = Boolean.TRUE.equals(params.get("usesHybridComposition"));
webViewBuilder
.setUsesHybridComposition(usesHybridComposition)
@@ -164,8 +176,9 @@
.setJavaScriptCanOpenWindowsAutomatically(
true) // Always allow automatically opening of windows.
.setSupportMultipleWindows(true) // Always support multiple windows.
- .setWebChromeClient(
- webChromeClient); // Always use {@link FlutterWebChromeClient} as web Chrome client.
+ .setWebChromeClient(webChromeClient)
+ .setDownloadListener(
+ downloadListener); // Always use {@link FlutterWebChromeClient} as web Chrome client.
return webViewBuilder.build();
}
diff --git a/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java b/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java
index adc8467..260ef8e 100644
--- a/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java
+++ b/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java
@@ -115,6 +115,22 @@
return true;
}
+ /**
+ * Notifies the Flutter code that a download should start when a navigation delegate is set.
+ *
+ * @param view the webView the result of the navigation delegate will be send to.
+ * @param url the download url
+ * @return A boolean whether or not the request is forwarded to the Flutter code.
+ */
+ boolean notifyDownload(WebView view, String url) {
+ if (!hasNavigationDelegate) {
+ return false;
+ }
+
+ notifyOnNavigationRequest(url, null, view, true);
+ return true;
+ }
+
private void onPageStarted(WebView view, String url) {
Map<String, Object> args = new HashMap<>();
args.put("url", url);
diff --git a/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewBuilder.java b/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewBuilder.java
index 6b8cc51..d3cd1d5 100644
--- a/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewBuilder.java
+++ b/packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewBuilder.java
@@ -6,6 +6,7 @@
import android.content.Context;
import android.view.View;
+import android.webkit.DownloadListener;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
@@ -44,6 +45,7 @@
private boolean supportMultipleWindows;
private boolean usesHybridComposition;
private WebChromeClient webChromeClient;
+ private DownloadListener downloadListener;
/**
* Constructs a new {@link WebViewBuilder} object with a custom implementation of the {@link
@@ -123,6 +125,18 @@
}
/**
+ * Registers the interface to be used when content can not be handled by the rendering engine, and
+ * should be downloaded instead. This will replace the current handler.
+ *
+ * @param downloadListener an implementation of DownloadListener This value may be null.
+ * @return This builder. This value cannot be {@code null}.
+ */
+ public WebViewBuilder setDownloadListener(@Nullable DownloadListener downloadListener) {
+ this.downloadListener = downloadListener;
+ return this;
+ }
+
+ /**
* Build the {@link android.webkit.WebView} using the current settings.
*
* @return The {@link android.webkit.WebView} using the current settings.
@@ -135,7 +149,7 @@
webSettings.setJavaScriptCanOpenWindowsAutomatically(javaScriptCanOpenWindowsAutomatically);
webSettings.setSupportMultipleWindows(supportMultipleWindows);
webView.setWebChromeClient(webChromeClient);
-
+ webView.setDownloadListener(downloadListener);
return webView;
}
}
diff --git a/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterDownloadListenerTest.java b/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterDownloadListenerTest.java
new file mode 100644
index 0000000..2c91858
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterDownloadListenerTest.java
@@ -0,0 +1,42 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.webviewflutter;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.webkit.WebView;
+import org.junit.Before;
+import org.junit.Test;
+
+public class FlutterDownloadListenerTest {
+ private FlutterWebViewClient webViewClient;
+ private WebView webView;
+
+ @Before
+ public void before() {
+ webViewClient = mock(FlutterWebViewClient.class);
+ webView = mock(WebView.class);
+ }
+
+ @Test
+ public void onDownloadStart_should_notify_webViewClient() {
+ String url = "testurl.com";
+ FlutterDownloadListener downloadListener = new FlutterDownloadListener(webViewClient);
+ downloadListener.onDownloadStart(url, "test", "inline", "data/text", 0);
+ verify(webViewClient).notifyDownload(nullable(WebView.class), eq(url));
+ }
+
+ @Test
+ public void onDownloadStart_should_pass_webView() {
+ FlutterDownloadListener downloadListener = new FlutterDownloadListener(webViewClient);
+ downloadListener.setWebView(webView);
+ downloadListener.onDownloadStart("testurl.com", "test", "inline", "data/text", 0);
+ verify(webViewClient).notifyDownload(eq(webView), anyString());
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewClientTest.java b/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewClientTest.java
new file mode 100644
index 0000000..86346ac
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewClientTest.java
@@ -0,0 +1,60 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.webviewflutter;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+import android.webkit.WebView;
+import io.flutter.plugin.common.MethodChannel;
+import java.util.HashMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+public class FlutterWebViewClientTest {
+
+ MethodChannel mockMethodChannel;
+ WebView mockWebView;
+
+ @Before
+ public void before() {
+ mockMethodChannel = mock(MethodChannel.class);
+ mockWebView = mock(WebView.class);
+ }
+
+ @Test
+ public void notify_download_should_notifyOnNavigationRequest_when_navigationDelegate_is_set() {
+ final String url = "testurl.com";
+
+ FlutterWebViewClient client = new FlutterWebViewClient(mockMethodChannel);
+ client.createWebViewClient(true);
+
+ client.notifyDownload(mockWebView, url);
+ ArgumentCaptor<Object> argumentCaptor = ArgumentCaptor.forClass(Object.class);
+ verify(mockMethodChannel)
+ .invokeMethod(
+ eq("navigationRequest"), argumentCaptor.capture(), any(MethodChannel.Result.class));
+ HashMap<String, Object> map = (HashMap<String, Object>) argumentCaptor.getValue();
+ assertEquals(map.get("url"), url);
+ assertEquals(map.get("isForMainFrame"), true);
+ }
+
+ @Test
+ public void
+ notify_download_should_not_notifyOnNavigationRequest_when_navigationDelegate_is_not_set() {
+ final String url = "testurl.com";
+
+ FlutterWebViewClient client = new FlutterWebViewClient(mockMethodChannel);
+ client.createWebViewClient(false);
+
+ client.notifyDownload(mockWebView, url);
+ verifyNoInteractions(mockMethodChannel);
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewTest.java b/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewTest.java
index 96cbdec..56d9db1 100644
--- a/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewTest.java
+++ b/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewTest.java
@@ -11,6 +11,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.webkit.DownloadListener;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import java.util.HashMap;
@@ -20,6 +21,7 @@
public class FlutterWebViewTest {
private WebChromeClient mockWebChromeClient;
+ private DownloadListener mockDownloadListener;
private WebViewBuilder mockWebViewBuilder;
private WebView mockWebView;
@@ -28,6 +30,7 @@
mockWebChromeClient = mock(WebChromeClient.class);
mockWebViewBuilder = mock(WebViewBuilder.class);
mockWebView = mock(WebView.class);
+ mockDownloadListener = mock(DownloadListener.class);
when(mockWebViewBuilder.setDomStorageEnabled(anyBoolean())).thenReturn(mockWebViewBuilder);
when(mockWebViewBuilder.setJavaScriptCanOpenWindowsAutomatically(anyBoolean()))
@@ -36,6 +39,8 @@
when(mockWebViewBuilder.setUsesHybridComposition(anyBoolean())).thenReturn(mockWebViewBuilder);
when(mockWebViewBuilder.setWebChromeClient(any(WebChromeClient.class)))
.thenReturn(mockWebViewBuilder);
+ when(mockWebViewBuilder.setDownloadListener(any(DownloadListener.class)))
+ .thenReturn(mockWebViewBuilder);
when(mockWebViewBuilder.build()).thenReturn(mockWebView);
}
@@ -43,7 +48,7 @@
@Test
public void createWebView_should_create_webview_with_default_configuration() {
FlutterWebView.createWebView(
- mockWebViewBuilder, createParameterMap(false), mockWebChromeClient);
+ mockWebViewBuilder, createParameterMap(false), mockWebChromeClient, mockDownloadListener);
verify(mockWebViewBuilder, times(1)).setDomStorageEnabled(true);
verify(mockWebViewBuilder, times(1)).setJavaScriptCanOpenWindowsAutomatically(true);
diff --git a/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewBuilderTest.java b/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewBuilderTest.java
index 48fbce2..423cb21 100644
--- a/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewBuilderTest.java
+++ b/packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewBuilderTest.java
@@ -9,6 +9,7 @@
import android.content.Context;
import android.view.View;
+import android.webkit.DownloadListener;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
@@ -60,6 +61,7 @@
public void build_should_set_values() throws IOException {
WebSettings mockWebSettings = mock(WebSettings.class);
WebChromeClient mockWebChromeClient = mock(WebChromeClient.class);
+ DownloadListener mockDownloadListener = mock(DownloadListener.class);
when(mockWebView.getSettings()).thenReturn(mockWebSettings);
@@ -68,7 +70,8 @@
.setDomStorageEnabled(true)
.setJavaScriptCanOpenWindowsAutomatically(true)
.setSupportMultipleWindows(true)
- .setWebChromeClient(mockWebChromeClient);
+ .setWebChromeClient(mockWebChromeClient)
+ .setDownloadListener(mockDownloadListener);
WebView webView = builder.build();
@@ -77,6 +80,7 @@
verify(mockWebSettings).setJavaScriptCanOpenWindowsAutomatically(true);
verify(mockWebSettings).setSupportMultipleWindows(true);
verify(mockWebView).setWebChromeClient(mockWebChromeClient);
+ verify(mockWebView).setDownloadListener(mockDownloadListener);
}
@Test
@@ -95,5 +99,6 @@
verify(mockWebSettings).setJavaScriptCanOpenWindowsAutomatically(false);
verify(mockWebSettings).setSupportMultipleWindows(false);
verify(mockWebView).setWebChromeClient(null);
+ verify(mockWebView).setDownloadListener(null);
}
}
diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml
index cc5d9cd..3976ff7 100644
--- a/packages/webview_flutter/webview_flutter/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter/pubspec.yaml
@@ -2,7 +2,7 @@
description: A Flutter plugin that provides a WebView widget on Android and iOS.
repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.0.12
+version: 2.0.13
environment:
sdk: ">=2.12.0 <3.0.0"