Created
January 30, 2021 16:30
-
-
Save tneotia/ccdb84ba66d6863980b9d558fb95312e to your computer and use it in GitHub Desktop.
Code to fix file uploads in webview_flutter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2018 The Chromium 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.annotation.TargetApi; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.net.Uri; | |
import android.webkit.ValueCallback; | |
import android.hardware.display.DisplayManager; | |
import android.os.Build; | |
import android.os.Handler; | |
import android.os.Message; | |
import android.view.View; | |
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 io.flutter.plugin.common.BinaryMessenger; | |
import io.flutter.plugin.common.MethodCall; | |
import io.flutter.plugin.common.MethodChannel; | |
import io.flutter.plugin.common.MethodChannel.MethodCallHandler; | |
import io.flutter.plugin.common.MethodChannel.Result; | |
import io.flutter.plugin.platform.PlatformView; | |
import java.util.Collections; | |
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 InputAwareWebView webView; | |
private final MethodChannel methodChannel; | |
private final FlutterWebViewClient flutterWebViewClient; | |
private final Handler platformThreadHandler; | |
Context context1; | |
// Verifies that a url opened by `Window.open` has a secure url. | |
private class FlutterWebChromeClient extends WebChromeClient { | |
@Override | |
public boolean onCreateWindow( | |
final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { | |
final WebViewClient webViewClient = | |
new WebViewClient() { | |
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
@Override | |
public boolean shouldOverrideUrlLoading( | |
@NonNull WebView view, @NonNull WebResourceRequest request) { | |
final String url = request.getUrl().toString(); | |
if (!flutterWebViewClient.shouldOverrideUrlLoading( | |
FlutterWebView.this.webView, request)) { | |
webView.loadUrl(url); | |
} | |
return true; | |
} | |
@Override | |
public boolean shouldOverrideUrlLoading(WebView view, String url) { | |
if (!flutterWebViewClient.shouldOverrideUrlLoading( | |
FlutterWebView.this.webView, url)) { | |
webView.loadUrl(url); | |
} | |
return true; | |
} | |
}; | |
final WebView newWebView = new WebView(view.getContext()); | |
newWebView.setWebViewClient(webViewClient); | |
final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; | |
transport.setWebView(newWebView); | |
resultMsg.sendToTarget(); | |
return true; | |
} | |
@Override | |
public boolean onShowFileChooser( | |
WebView webView, ValueCallback<Uri[]> filePathCallback, | |
WebChromeClient.FileChooserParams fileChooserParams) { | |
Intent intent = new Intent(context1, newActivity.class); | |
newActivity.getfilePathCallback(filePathCallback); | |
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
context1.startActivity(intent); | |
return true; | |
} | |
} | |
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) | |
@SuppressWarnings("unchecked") | |
FlutterWebView( | |
final Context context, | |
BinaryMessenger messenger, | |
int id, | |
Map<String, Object> params, | |
View containerView) { | |
DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy(); | |
DisplayManager displayManager = | |
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); | |
displayListenerProxy.onPreWebViewInitialization(displayManager); | |
webView = new InputAwareWebView(context, containerView); | |
displayListenerProxy.onPostWebViewInitialization(displayManager); | |
platformThreadHandler = new Handler(context.getMainLooper()); | |
// Allow local storage. | |
webView.getSettings().setDomStorageEnabled(true); | |
webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true); | |
context1 = context; | |
webView.getSettings().setAllowContentAccess(true); | |
webView.getSettings().setAllowFileAccess(true); | |
// Multi windows is set with FlutterWebChromeClient by default to handle internal bug: b/159892679. | |
webView.getSettings().setSupportMultipleWindows(true); | |
webView.setWebChromeClient(new FlutterWebChromeClient()); | |
methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id); | |
methodChannel.setMethodCallHandler(this); | |
flutterWebViewClient = new FlutterWebViewClient(methodChannel); | |
Map<String, Object> settings = (Map<String, Object>) params.get("settings"); | |
if (settings != null) applySettings(settings); | |
if (params.containsKey(JS_CHANNEL_NAMES_FIELD)) { | |
List<String> names = (List<String>) params.get(JS_CHANNEL_NAMES_FIELD); | |
if (names != null) registerJavaScriptChannelNames(names); | |
} | |
Integer autoMediaPlaybackPolicy = (Integer) params.get("autoMediaPlaybackPolicy"); | |
if (autoMediaPlaybackPolicy != null) updateAutoMediaPlaybackPolicy(autoMediaPlaybackPolicy); | |
if (params.containsKey("userAgent")) { | |
String userAgent = (String) params.get("userAgent"); | |
updateUserAgent(userAgent); | |
} | |
if (params.containsKey("initialUrl")) { | |
String url = (String) params.get("initialUrl"); | |
webView.loadUrl(url); | |
} | |
} | |
@Override | |
public View getView() { | |
return webView; | |
} | |
// @Override | |
// This is overriding a method that hasn't rolled into stable Flutter yet. Including the | |
// annotation would cause compile time failures in versions of Flutter too old to include the new | |
// method. However leaving it raw like this means that the method will be ignored in old versions | |
// of Flutter but used as an override anyway wherever it's actually defined. | |
// TODO(mklim): Add the @Override annotation once flutter/engine#9727 rolls to stable. | |
public void onInputConnectionUnlocked() { | |
webView.unlockInputConnection(); | |
} | |
// @Override | |
// This is overriding a method that hasn't rolled into stable Flutter yet. Including the | |
// annotation would cause compile time failures in versions of Flutter too old to include the new | |
// method. However leaving it raw like this means that the method will be ignored in old versions | |
// of Flutter but used as an override anyway wherever it's actually defined. | |
// TODO(mklim): Add the @Override annotation once flutter/engine#9727 rolls to stable. | |
public void onInputConnectionLocked() { | |
webView.lockInputConnection(); | |
} | |
// @Override | |
// This is overriding a method that hasn't rolled into stable Flutter yet. Including the | |
// annotation would cause compile time failures in versions of Flutter too old to include the new | |
// method. However leaving it raw like this means that the method will be ignored in old versions | |
// of Flutter but used as an override anyway wherever it's actually defined. | |
// TODO(mklim): Add the @Override annotation once stable passes v1.10.9. | |
public void onFlutterViewAttached(View flutterView) { | |
webView.setContainerView(flutterView); | |
} | |
// @Override | |
// This is overriding a method that hasn't rolled into stable Flutter yet. Including the | |
// annotation would cause compile time failures in versions of Flutter too old to include the new | |
// method. However leaving it raw like this means that the method will be ignored in old versions | |
// of Flutter but used as an override anyway wherever it's actually defined. | |
// TODO(mklim): Add the @Override annotation once stable passes v1.10.9. | |
public void onFlutterViewDetached() { | |
webView.setContainerView(null); | |
} | |
@Override | |
public void onMethodCall(MethodCall methodCall, Result result) { | |
switch (methodCall.method) { | |
case "loadUrl": | |
loadUrl(methodCall, result); | |
break; | |
case "updateSettings": | |
updateSettings(methodCall, result); | |
break; | |
case "canGoBack": | |
canGoBack(result); | |
break; | |
case "canGoForward": | |
canGoForward(result); | |
break; | |
case "goBack": | |
goBack(result); | |
break; | |
case "goForward": | |
goForward(result); | |
break; | |
case "reload": | |
reload(result); | |
break; | |
case "currentUrl": | |
currentUrl(result); | |
break; | |
case "evaluateJavascript": | |
evaluateJavaScript(methodCall, result); | |
break; | |
case "addJavascriptChannels": | |
addJavaScriptChannels(methodCall, result); | |
break; | |
case "removeJavascriptChannels": | |
removeJavaScriptChannels(methodCall, result); | |
break; | |
case "clearCache": | |
clearCache(result); | |
break; | |
case "getTitle": | |
getTitle(result); | |
break; | |
case "scrollTo": | |
scrollTo(methodCall, result); | |
break; | |
case "scrollBy": | |
scrollBy(methodCall, result); | |
break; | |
case "getScrollX": | |
getScrollX(result); | |
break; | |
case "getScrollY": | |
getScrollY(result); | |
break; | |
default: | |
result.notImplemented(); | |
} | |
} | |
@SuppressWarnings("unchecked") | |
private void loadUrl(MethodCall methodCall, Result result) { | |
Map<String, Object> request = (Map<String, Object>) methodCall.arguments; | |
String url = (String) request.get("url"); | |
Map<String, String> headers = (Map<String, String>) request.get("headers"); | |
if (headers == null) { | |
headers = Collections.emptyMap(); | |
} | |
webView.loadUrl(url, headers); | |
result.success(null); | |
} | |
private void canGoBack(Result result) { | |
result.success(webView.canGoBack()); | |
} | |
private void canGoForward(Result result) { | |
result.success(webView.canGoForward()); | |
} | |
private void goBack(Result result) { | |
if (webView.canGoBack()) { | |
webView.goBack(); | |
} | |
result.success(null); | |
} | |
private void goForward(Result result) { | |
if (webView.canGoForward()) { | |
webView.goForward(); | |
} | |
result.success(null); | |
} | |
private void reload(Result result) { | |
webView.reload(); | |
result.success(null); | |
} | |
private void currentUrl(Result result) { | |
result.success(webView.getUrl()); | |
} | |
@SuppressWarnings("unchecked") | |
private void updateSettings(MethodCall methodCall, Result result) { | |
applySettings((Map<String, Object>) methodCall.arguments); | |
result.success(null); | |
} | |
@TargetApi(Build.VERSION_CODES.KITKAT) | |
private void evaluateJavaScript(MethodCall methodCall, final Result result) { | |
String jsString = (String) methodCall.arguments; | |
if (jsString == null) { | |
throw new UnsupportedOperationException("JavaScript string cannot be null"); | |
} | |
webView.evaluateJavascript( | |
jsString, | |
new android.webkit.ValueCallback<String>() { | |
@Override | |
public void onReceiveValue(String value) { | |
result.success(value); | |
} | |
}); | |
} | |
@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 clearCache(Result result) { | |
webView.clearCache(true); | |
WebStorage.getInstance().deleteAllData(); | |
result.success(null); | |
} | |
private void getTitle(Result result) { | |
result.success(webView.getTitle()); | |
} | |
private void scrollTo(MethodCall methodCall, Result result) { | |
Map<String, Object> request = methodCall.arguments(); | |
int x = (int) request.get("x"); | |
int y = (int) request.get("y"); | |
webView.scrollTo(x, y); | |
result.success(null); | |
} | |
private void scrollBy(MethodCall methodCall, Result result) { | |
Map<String, Object> request = methodCall.arguments(); | |
int x = (int) request.get("x"); | |
int y = (int) request.get("y"); | |
webView.scrollBy(x, y); | |
result.success(null); | |
} | |
private void getScrollX(Result result) { | |
result.success(webView.getScrollX()); | |
} | |
private void getScrollY(Result result) { | |
result.success(webView.getScrollY()); | |
} | |
private void applySettings(Map<String, Object> settings) { | |
for (String key : settings.keySet()) { | |
switch (key) { | |
case "jsMode": | |
Integer mode = (Integer) settings.get(key); | |
if (mode != null) updateJsMode(mode); | |
break; | |
case "hasNavigationDelegate": | |
final boolean hasNavigationDelegate = (boolean) settings.get(key); | |
final WebViewClient webViewClient = | |
flutterWebViewClient.createWebViewClient(hasNavigationDelegate); | |
webView.setWebViewClient(webViewClient); | |
break; | |
case "debuggingEnabled": | |
final boolean debuggingEnabled = (boolean) settings.get(key); | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | |
webView.setWebContentsDebuggingEnabled(debuggingEnabled); | |
} | |
break; | |
case "gestureNavigationEnabled": | |
break; | |
case "userAgent": | |
updateUserAgent((String) settings.get(key)); | |
break; | |
default: | |
throw new IllegalArgumentException("Unknown WebView setting: " + key); | |
} | |
} | |
} | |
private void updateJsMode(int mode) { | |
switch (mode) { | |
case 0: // disabled | |
webView.getSettings().setJavaScriptEnabled(false); | |
break; | |
case 1: // unrestricted | |
webView.getSettings().setJavaScriptEnabled(true); | |
break; | |
default: | |
throw new IllegalArgumentException("Trying to set unknown JavaScript mode: " + mode); | |
} | |
} | |
private void updateAutoMediaPlaybackPolicy(int mode) { | |
// This is the index of the AutoMediaPlaybackPolicy enum, index 1 is always_allow, for all | |
// other values we require a user gesture. | |
boolean requireUserGesture = mode != 1; | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { | |
webView.getSettings().setMediaPlaybackRequiresUserGesture(requireUserGesture); | |
} | |
} | |
private void registerJavaScriptChannelNames(List<String> channelNames) { | |
for (String channelName : channelNames) { | |
webView.addJavascriptInterface( | |
new JavaScriptChannel(methodChannel, channelName, platformThreadHandler), channelName); | |
} | |
} | |
private void updateUserAgent(String userAgent) { | |
webView.getSettings().setUserAgentString(userAgent); | |
} | |
@Override | |
public void dispose() { | |
methodChannel.setMethodCallHandler(null); | |
webView.dispose(); | |
webView.destroy(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment