diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md index 361bfd24f3af..1e1d5aa523ba 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 000000000000..cfad4e315514 --- /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 a3b681f27980..4651a5f5ae22 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 @@ public void onProgressChanged(WebView view, int progress) { (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 settings = (Map) params.get("settings"); if (settings != null) { applySettings(settings); @@ -156,7 +165,10 @@ public void onProgressChanged(WebView view, int progress) { */ @VisibleForTesting static WebView createWebView( - WebViewBuilder webViewBuilder, Map params, WebChromeClient webChromeClient) { + WebViewBuilder webViewBuilder, + Map params, + WebChromeClient webChromeClient, + @Nullable DownloadListener downloadListener) { boolean usesHybridComposition = Boolean.TRUE.equals(params.get("usesHybridComposition")); webViewBuilder .setUsesHybridComposition(usesHybridComposition) @@ -164,8 +176,9 @@ static WebView createWebView( .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 adc84671a701..260ef8e8b15d 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 @@ boolean shouldOverrideUrlLoading(WebView view, String url) { 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 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 6b8cc51febe8..d3cd1d57cdae 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 @@ static WebView create(Context context, boolean usesHybridComposition, View conta 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 @@ -122,6 +124,18 @@ public WebViewBuilder setWebChromeClient(@Nullable WebChromeClient webChromeClie return this; } + /** + * 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. * @@ -135,7 +149,7 @@ public WebView build() { 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 000000000000..2c918584ba83 --- /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 000000000000..86346ac08f16 --- /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 argumentCaptor = ArgumentCaptor.forClass(Object.class); + verify(mockMethodChannel) + .invokeMethod( + eq("navigationRequest"), argumentCaptor.capture(), any(MethodChannel.Result.class)); + HashMap map = (HashMap) 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 96cbdece387c..56d9db1ee493 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 @@ public void before() { 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 @@ public void before() { 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 @@ public void before() { @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 48fbce231ed5..423cb210c392 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 ctor_test() { 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 @@ public void build_should_set_values() throws IOException { .setDomStorageEnabled(true) .setJavaScriptCanOpenWindowsAutomatically(true) .setSupportMultipleWindows(true) - .setWebChromeClient(mockWebChromeClient); + .setWebChromeClient(mockWebChromeClient) + .setDownloadListener(mockDownloadListener); WebView webView = builder.build(); @@ -77,6 +80,7 @@ public void build_should_set_values() throws IOException { verify(mockWebSettings).setJavaScriptCanOpenWindowsAutomatically(true); verify(mockWebSettings).setSupportMultipleWindows(true); verify(mockWebView).setWebChromeClient(mockWebChromeClient); + verify(mockWebView).setDownloadListener(mockDownloadListener); } @Test @@ -95,5 +99,6 @@ public void build_should_use_default_values() throws IOException { 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 cc5d9cdc8b96..3976ff74fef6 100644 --- a/packages/webview_flutter/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter 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"