Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 5e4b5e0

Browse files
authored
[webview_flutter] Add download listener to Android webview (#4322)
1 parent d5b6574 commit 5e4b5e0

File tree

10 files changed

+202
-13
lines changed

10 files changed

+202
-13
lines changed

packages/webview_flutter/webview_flutter/CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## NEXT
1+
## 2.0.13
22

3+
* Send URL of File to download to the NavigationDelegate on Android just like it is already done on iOS.
34
* Updated Android lint settings.
45

56
## 2.0.12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutter;
6+
7+
import android.webkit.DownloadListener;
8+
import android.webkit.WebView;
9+
10+
/** DownloadListener to notify the {@link FlutterWebViewClient} of download starts */
11+
public class FlutterDownloadListener implements DownloadListener {
12+
private final FlutterWebViewClient webViewClient;
13+
private WebView webView;
14+
15+
public FlutterDownloadListener(FlutterWebViewClient webViewClient) {
16+
this.webViewClient = webViewClient;
17+
}
18+
19+
/** Sets the {@link WebView} that the result of the navigation delegate will be send to. */
20+
public void setWebView(WebView webView) {
21+
this.webView = webView;
22+
}
23+
24+
@Override
25+
public void onDownloadStart(
26+
String url,
27+
String userAgent,
28+
String contentDisposition,
29+
String mimetype,
30+
long contentLength) {
31+
webViewClient.notifyDownload(webView, url);
32+
}
33+
}

packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java

+21-8
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111
import android.os.Handler;
1212
import android.os.Message;
1313
import android.view.View;
14+
import android.webkit.DownloadListener;
1415
import android.webkit.WebChromeClient;
1516
import android.webkit.WebResourceRequest;
1617
import android.webkit.WebStorage;
1718
import android.webkit.WebView;
1819
import android.webkit.WebViewClient;
1920
import androidx.annotation.NonNull;
21+
import androidx.annotation.Nullable;
2022
import androidx.annotation.VisibleForTesting;
2123
import io.flutter.plugin.common.MethodCall;
2224
import io.flutter.plugin.common.MethodChannel;
@@ -94,18 +96,25 @@ public void onProgressChanged(WebView view, int progress) {
9496
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
9597
displayListenerProxy.onPreWebViewInitialization(displayManager);
9698

99+
this.methodChannel = methodChannel;
100+
this.methodChannel.setMethodCallHandler(this);
101+
102+
flutterWebViewClient = new FlutterWebViewClient(methodChannel);
103+
104+
FlutterDownloadListener flutterDownloadListener =
105+
new FlutterDownloadListener(flutterWebViewClient);
97106
webView =
98107
createWebView(
99-
new WebViewBuilder(context, containerView), params, new FlutterWebChromeClient());
108+
new WebViewBuilder(context, containerView),
109+
params,
110+
new FlutterWebChromeClient(),
111+
flutterDownloadListener);
112+
flutterDownloadListener.setWebView(webView);
100113

101114
displayListenerProxy.onPostWebViewInitialization(displayManager);
102115

103116
platformThreadHandler = new Handler(context.getMainLooper());
104117

105-
this.methodChannel = methodChannel;
106-
this.methodChannel.setMethodCallHandler(this);
107-
108-
flutterWebViewClient = new FlutterWebViewClient(methodChannel);
109118
Map<String, Object> settings = (Map<String, Object>) params.get("settings");
110119
if (settings != null) {
111120
applySettings(settings);
@@ -156,16 +165,20 @@ public void onProgressChanged(WebView view, int progress) {
156165
*/
157166
@VisibleForTesting
158167
static WebView createWebView(
159-
WebViewBuilder webViewBuilder, Map<String, Object> params, WebChromeClient webChromeClient) {
168+
WebViewBuilder webViewBuilder,
169+
Map<String, Object> params,
170+
WebChromeClient webChromeClient,
171+
@Nullable DownloadListener downloadListener) {
160172
boolean usesHybridComposition = Boolean.TRUE.equals(params.get("usesHybridComposition"));
161173
webViewBuilder
162174
.setUsesHybridComposition(usesHybridComposition)
163175
.setDomStorageEnabled(true) // Always enable DOM storage API.
164176
.setJavaScriptCanOpenWindowsAutomatically(
165177
true) // Always allow automatically opening of windows.
166178
.setSupportMultipleWindows(true) // Always support multiple windows.
167-
.setWebChromeClient(
168-
webChromeClient); // Always use {@link FlutterWebChromeClient} as web Chrome client.
179+
.setWebChromeClient(webChromeClient)
180+
.setDownloadListener(
181+
downloadListener); // Always use {@link FlutterWebChromeClient} as web Chrome client.
169182

170183
return webViewBuilder.build();
171184
}

packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java

+16
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,22 @@ boolean shouldOverrideUrlLoading(WebView view, String url) {
115115
return true;
116116
}
117117

118+
/**
119+
* Notifies the Flutter code that a download should start when a navigation delegate is set.
120+
*
121+
* @param view the webView the result of the navigation delegate will be send to.
122+
* @param url the download url
123+
* @return A boolean whether or not the request is forwarded to the Flutter code.
124+
*/
125+
boolean notifyDownload(WebView view, String url) {
126+
if (!hasNavigationDelegate) {
127+
return false;
128+
}
129+
130+
notifyOnNavigationRequest(url, null, view, true);
131+
return true;
132+
}
133+
118134
private void onPageStarted(WebView view, String url) {
119135
Map<String, Object> args = new HashMap<>();
120136
args.put("url", url);

packages/webview_flutter/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewBuilder.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import android.content.Context;
88
import android.view.View;
9+
import android.webkit.DownloadListener;
910
import android.webkit.WebChromeClient;
1011
import android.webkit.WebSettings;
1112
import android.webkit.WebView;
@@ -44,6 +45,7 @@ static WebView create(Context context, boolean usesHybridComposition, View conta
4445
private boolean supportMultipleWindows;
4546
private boolean usesHybridComposition;
4647
private WebChromeClient webChromeClient;
48+
private DownloadListener downloadListener;
4749

4850
/**
4951
* Constructs a new {@link WebViewBuilder} object with a custom implementation of the {@link
@@ -122,6 +124,18 @@ public WebViewBuilder setWebChromeClient(@Nullable WebChromeClient webChromeClie
122124
return this;
123125
}
124126

127+
/**
128+
* Registers the interface to be used when content can not be handled by the rendering engine, and
129+
* should be downloaded instead. This will replace the current handler.
130+
*
131+
* @param downloadListener an implementation of DownloadListener This value may be null.
132+
* @return This builder. This value cannot be {@code null}.
133+
*/
134+
public WebViewBuilder setDownloadListener(@Nullable DownloadListener downloadListener) {
135+
this.downloadListener = downloadListener;
136+
return this;
137+
}
138+
125139
/**
126140
* Build the {@link android.webkit.WebView} using the current settings.
127141
*
@@ -135,7 +149,7 @@ public WebView build() {
135149
webSettings.setJavaScriptCanOpenWindowsAutomatically(javaScriptCanOpenWindowsAutomatically);
136150
webSettings.setSupportMultipleWindows(supportMultipleWindows);
137151
webView.setWebChromeClient(webChromeClient);
138-
152+
webView.setDownloadListener(downloadListener);
139153
return webView;
140154
}
141155
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutter;
6+
7+
import static org.mockito.ArgumentMatchers.anyString;
8+
import static org.mockito.ArgumentMatchers.eq;
9+
import static org.mockito.ArgumentMatchers.nullable;
10+
import static org.mockito.Mockito.mock;
11+
import static org.mockito.Mockito.verify;
12+
13+
import android.webkit.WebView;
14+
import org.junit.Before;
15+
import org.junit.Test;
16+
17+
public class FlutterDownloadListenerTest {
18+
private FlutterWebViewClient webViewClient;
19+
private WebView webView;
20+
21+
@Before
22+
public void before() {
23+
webViewClient = mock(FlutterWebViewClient.class);
24+
webView = mock(WebView.class);
25+
}
26+
27+
@Test
28+
public void onDownloadStart_should_notify_webViewClient() {
29+
String url = "testurl.com";
30+
FlutterDownloadListener downloadListener = new FlutterDownloadListener(webViewClient);
31+
downloadListener.onDownloadStart(url, "test", "inline", "data/text", 0);
32+
verify(webViewClient).notifyDownload(nullable(WebView.class), eq(url));
33+
}
34+
35+
@Test
36+
public void onDownloadStart_should_pass_webView() {
37+
FlutterDownloadListener downloadListener = new FlutterDownloadListener(webViewClient);
38+
downloadListener.setWebView(webView);
39+
downloadListener.onDownloadStart("testurl.com", "test", "inline", "data/text", 0);
40+
verify(webViewClient).notifyDownload(eq(webView), anyString());
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutter;
6+
7+
import static org.junit.Assert.assertEquals;
8+
import static org.mockito.ArgumentMatchers.any;
9+
import static org.mockito.ArgumentMatchers.eq;
10+
import static org.mockito.Mockito.mock;
11+
import static org.mockito.Mockito.verify;
12+
import static org.mockito.Mockito.verifyNoInteractions;
13+
14+
import android.webkit.WebView;
15+
import io.flutter.plugin.common.MethodChannel;
16+
import java.util.HashMap;
17+
import org.junit.Before;
18+
import org.junit.Test;
19+
import org.mockito.ArgumentCaptor;
20+
21+
public class FlutterWebViewClientTest {
22+
23+
MethodChannel mockMethodChannel;
24+
WebView mockWebView;
25+
26+
@Before
27+
public void before() {
28+
mockMethodChannel = mock(MethodChannel.class);
29+
mockWebView = mock(WebView.class);
30+
}
31+
32+
@Test
33+
public void notify_download_should_notifyOnNavigationRequest_when_navigationDelegate_is_set() {
34+
final String url = "testurl.com";
35+
36+
FlutterWebViewClient client = new FlutterWebViewClient(mockMethodChannel);
37+
client.createWebViewClient(true);
38+
39+
client.notifyDownload(mockWebView, url);
40+
ArgumentCaptor<Object> argumentCaptor = ArgumentCaptor.forClass(Object.class);
41+
verify(mockMethodChannel)
42+
.invokeMethod(
43+
eq("navigationRequest"), argumentCaptor.capture(), any(MethodChannel.Result.class));
44+
HashMap<String, Object> map = (HashMap<String, Object>) argumentCaptor.getValue();
45+
assertEquals(map.get("url"), url);
46+
assertEquals(map.get("isForMainFrame"), true);
47+
}
48+
49+
@Test
50+
public void
51+
notify_download_should_not_notifyOnNavigationRequest_when_navigationDelegate_is_not_set() {
52+
final String url = "testurl.com";
53+
54+
FlutterWebViewClient client = new FlutterWebViewClient(mockMethodChannel);
55+
client.createWebViewClient(false);
56+
57+
client.notifyDownload(mockWebView, url);
58+
verifyNoInteractions(mockMethodChannel);
59+
}
60+
}

packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterWebViewTest.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import static org.mockito.Mockito.verify;
1212
import static org.mockito.Mockito.when;
1313

14+
import android.webkit.DownloadListener;
1415
import android.webkit.WebChromeClient;
1516
import android.webkit.WebView;
1617
import java.util.HashMap;
@@ -20,6 +21,7 @@
2021

2122
public class FlutterWebViewTest {
2223
private WebChromeClient mockWebChromeClient;
24+
private DownloadListener mockDownloadListener;
2325
private WebViewBuilder mockWebViewBuilder;
2426
private WebView mockWebView;
2527

@@ -28,6 +30,7 @@ public void before() {
2830
mockWebChromeClient = mock(WebChromeClient.class);
2931
mockWebViewBuilder = mock(WebViewBuilder.class);
3032
mockWebView = mock(WebView.class);
33+
mockDownloadListener = mock(DownloadListener.class);
3134

3235
when(mockWebViewBuilder.setDomStorageEnabled(anyBoolean())).thenReturn(mockWebViewBuilder);
3336
when(mockWebViewBuilder.setJavaScriptCanOpenWindowsAutomatically(anyBoolean()))
@@ -36,14 +39,16 @@ public void before() {
3639
when(mockWebViewBuilder.setUsesHybridComposition(anyBoolean())).thenReturn(mockWebViewBuilder);
3740
when(mockWebViewBuilder.setWebChromeClient(any(WebChromeClient.class)))
3841
.thenReturn(mockWebViewBuilder);
42+
when(mockWebViewBuilder.setDownloadListener(any(DownloadListener.class)))
43+
.thenReturn(mockWebViewBuilder);
3944

4045
when(mockWebViewBuilder.build()).thenReturn(mockWebView);
4146
}
4247

4348
@Test
4449
public void createWebView_should_create_webview_with_default_configuration() {
4550
FlutterWebView.createWebView(
46-
mockWebViewBuilder, createParameterMap(false), mockWebChromeClient);
51+
mockWebViewBuilder, createParameterMap(false), mockWebChromeClient, mockDownloadListener);
4752

4853
verify(mockWebViewBuilder, times(1)).setDomStorageEnabled(true);
4954
verify(mockWebViewBuilder, times(1)).setJavaScriptCanOpenWindowsAutomatically(true);

packages/webview_flutter/webview_flutter/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewBuilderTest.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import android.content.Context;
1111
import android.view.View;
12+
import android.webkit.DownloadListener;
1213
import android.webkit.WebChromeClient;
1314
import android.webkit.WebSettings;
1415
import android.webkit.WebView;
@@ -60,6 +61,7 @@ public void ctor_test() {
6061
public void build_should_set_values() throws IOException {
6162
WebSettings mockWebSettings = mock(WebSettings.class);
6263
WebChromeClient mockWebChromeClient = mock(WebChromeClient.class);
64+
DownloadListener mockDownloadListener = mock(DownloadListener.class);
6365

6466
when(mockWebView.getSettings()).thenReturn(mockWebSettings);
6567

@@ -68,7 +70,8 @@ public void build_should_set_values() throws IOException {
6870
.setDomStorageEnabled(true)
6971
.setJavaScriptCanOpenWindowsAutomatically(true)
7072
.setSupportMultipleWindows(true)
71-
.setWebChromeClient(mockWebChromeClient);
73+
.setWebChromeClient(mockWebChromeClient)
74+
.setDownloadListener(mockDownloadListener);
7275

7376
WebView webView = builder.build();
7477

@@ -77,6 +80,7 @@ public void build_should_set_values() throws IOException {
7780
verify(mockWebSettings).setJavaScriptCanOpenWindowsAutomatically(true);
7881
verify(mockWebSettings).setSupportMultipleWindows(true);
7982
verify(mockWebView).setWebChromeClient(mockWebChromeClient);
83+
verify(mockWebView).setDownloadListener(mockDownloadListener);
8084
}
8185

8286
@Test
@@ -95,5 +99,6 @@ public void build_should_use_default_values() throws IOException {
9599
verify(mockWebSettings).setJavaScriptCanOpenWindowsAutomatically(false);
96100
verify(mockWebSettings).setSupportMultipleWindows(false);
97101
verify(mockWebView).setWebChromeClient(null);
102+
verify(mockWebView).setDownloadListener(null);
98103
}
99104
}

packages/webview_flutter/webview_flutter/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: webview_flutter
22
description: A Flutter plugin that provides a WebView widget on Android and iOS.
33
repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
5-
version: 2.0.12
5+
version: 2.0.13
66

77
environment:
88
sdk: ">=2.12.0 <3.0.0"

0 commit comments

Comments
 (0)