Skip to content

Instantly share code, notes, and snippets.

@Aulig
Last active March 15, 2021 15:47
Show Gist options
  • Save Aulig/792bbeffa50d3a3a88ee5123f80b9bb3 to your computer and use it in GitHub Desktop.
Save Aulig/792bbeffa50d3a3a88ee5123f80b9bb3 to your computer and use it in GitHub Desktop.
Flutter PR 3225 add support for picking multiple files
// Copyright 2019 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;
public class Constants {
static final String ACTION_REQUEST_CAMERA_PERMISSION_FINISHED =
"action_request_camera_permission_denied";
static final String ACTION_FILE_CHOOSER_FINISHED = "action_file_chooser_completed";
static final String EXTRA_TITLE = "extra_title";
static final String EXTRA_ACCEPT_TYPES = "extra_types";
static final String EXTRA_SHOW_VIDEO_OPTION = "extra_show_video_option";
static final String EXTRA_SHOW_IMAGE_OPTION = "extra_show_image_option";
static final String EXTRA_FILE_URIS = "extra_file_uris";
static final String EXTRA_ALLOW_MULTIPLE_FILES = "extra_allow_multiple_files";
static final String WEBVIEW_STORAGE_DIRECTORY = "storage";
}
// Copyright 2019 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 static io.flutter.plugins.webviewflutter.Constants.ACTION_FILE_CHOOSER_FINISHED;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ALLOW_MULTIPLE_FILES;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_FILE_URIS;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_IMAGE_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_VIDEO_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TITLE;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ACCEPT_TYPES;
import static io.flutter.plugins.webviewflutter.Constants.WEBVIEW_STORAGE_DIRECTORY;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
public class FileChooserActivity extends Activity {
private static final int FILE_CHOOSER_REQUEST_CODE = 12322;
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
// List of Uris that point to files where there MIGHT be the output of the capture. At most one of these can be valid
private final ArrayList<Uri> potentialCaptureOutputUris = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
showFileChooser(getIntent().getBooleanExtra(EXTRA_SHOW_IMAGE_OPTION, false), getIntent().getBooleanExtra(EXTRA_SHOW_VIDEO_OPTION, false));
}
private void showFileChooser(boolean showImageIntent, boolean showVideoIntent) {
Intent getContentIntent = createGetContentIntent();
Intent captureImageIntent = showImageIntent ? createCaptureIntent(MediaStore.ACTION_IMAGE_CAPTURE, "jpg") : null;
Intent captureVideoIntent = showVideoIntent ? createCaptureIntent(MediaStore.ACTION_VIDEO_CAPTURE, "mp4") : null;
if (getContentIntent == null && captureImageIntent == null && captureVideoIntent == null) {
// cannot open anything: cancel file chooser
sendBroadcast(new Intent(ACTION_FILE_CHOOSER_FINISHED));
finish();
} else {
ArrayList<Intent> intentList = new ArrayList<>();
if (getContentIntent != null) {
intentList.add(getContentIntent);
}
if (captureImageIntent != null) {
intentList.add(captureImageIntent);
}
if (captureVideoIntent != null) {
intentList.add(captureVideoIntent);
}
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_TITLE, getIntent().getStringExtra(EXTRA_TITLE));
chooserIntent.putExtra(Intent.EXTRA_INTENT, intentList.get(0));
intentList.remove(0);
if (intentList.size() > 0) {
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentList.toArray(new Intent[0]));
}
startActivityForResult(chooserIntent, FILE_CHOOSER_REQUEST_CODE);
}
}
private Intent createGetContentIntent() {
Intent filesIntent = new Intent(Intent.ACTION_GET_CONTENT);
if (getIntent().getBooleanExtra(EXTRA_ALLOW_MULTIPLE_FILES, false)) {
filesIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}
String[] acceptTypes = getIntent().getStringArrayExtra(EXTRA_ACCEPT_TYPES);
if (acceptTypes.length == 0 || (acceptTypes.length == 1 && acceptTypes[0].length() == 0)) {
// empty array or only 1 empty string? -> accept all types
filesIntent.setType("*/*");
} else if (acceptTypes.length == 1) {
filesIntent.setType(acceptTypes[0]);
} else {
// acceptTypes.length > 1
filesIntent.setType("*/*");
filesIntent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes);
}
return (filesIntent.resolveActivity(getPackageManager()) != null) ? filesIntent : null;
}
private Intent createCaptureIntent(String type, String fileFormat) {
Intent captureIntent = new Intent(type);
if (captureIntent.resolveActivity(getPackageManager()) == null) {
return null;
}
// Create the File where the output should go
Uri captureOutputUri = getTempUri(fileFormat);
potentialCaptureOutputUris.add(captureOutputUri);
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureOutputUri);
return captureIntent;
}
private File getStorageDirectory() {
File imageDirectory = new File(getCacheDir(), WEBVIEW_STORAGE_DIRECTORY);
if (!imageDirectory.exists() && !imageDirectory.mkdir()) {
Log.e("WEBVIEW", "Unable to create storage directory");
}
return imageDirectory;
}
private Uri getTempUri(String format) {
String fileName = "CAPTURE-" + simpleDateFormat.format(new Date()) + "." + format;
File file = new File(getStorageDirectory(), fileName);
return FileProvider.getUriForFile(
this, getApplicationContext().getPackageName() + ".generic.provider", file);
}
private String getFileNameFromUri(Uri uri) {
Cursor returnCursor = getContentResolver().query(uri, null, null, null, null);
assert returnCursor != null;
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
returnCursor.moveToFirst();
String name = returnCursor.getString(nameIndex);
returnCursor.close();
return name;
}
private Uri copyToLocalUri(Uri uri) {
File destination = new File(getStorageDirectory(), getFileNameFromUri(uri));
try (InputStream in = getContentResolver().openInputStream(uri); OutputStream out = new FileOutputStream(destination)) {
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".generic.provider", destination);
} catch (IOException e) {
Log.e("WEBVIEW", "Unable to copy selected image", e);
e.printStackTrace();
return null;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == FILE_CHOOSER_REQUEST_CODE) {
Intent fileChooserFinishedIntent = new Intent(ACTION_FILE_CHOOSER_FINISHED);
if (resultCode == Activity.RESULT_OK) {
if (data != null && (data.getDataString() != null || data.getClipData() != null)) {
if (data.getDataString() != null) {
// single result from file browser OR video from camera
Uri localUri = copyToLocalUri(data.getData());
if (localUri != null) {
fileChooserFinishedIntent.putExtra(EXTRA_FILE_URIS, new String[]{localUri.toString()});
}
} else if (data.getClipData() != null) {
// multiple results from file browser
int uriCount = data.getClipData().getItemCount();
String[] uriStrings = new String[uriCount];
for (int i = 0; i < uriCount; i++) {
Uri localUri = copyToLocalUri(data.getClipData().getItemAt(i).getUri());
if (localUri != null) {
uriStrings[i] = localUri.toString();
}
}
fileChooserFinishedIntent.putExtra(EXTRA_FILE_URIS, uriStrings);
}
} else {
// image result from camera (videos from the camera are handled above, but this if-branch could handle them too if this varies from device to device)
for (Uri captureOutputUri : potentialCaptureOutputUris) {
try {
// just opening an input stream (and closing immediately) to test if the Uri points to a valid file
// if it's not a real file, the below catch-clause gets executed and we continue with the next Uri in the loop.
getContentResolver().openInputStream(captureOutputUri).close();
fileChooserFinishedIntent.putExtra(EXTRA_FILE_URIS, new String[]{captureOutputUri.toString()});
// leave the loop, as only one of the potentialCaptureOutputUris is valid and we just found it
break;
} catch (IOException ignored) {}
}
}
}
sendBroadcast(fileChooserFinishedIntent);
finish();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
}
// Copyright 2019 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 static io.flutter.plugins.webviewflutter.Constants.ACTION_FILE_CHOOSER_FINISHED;
import static io.flutter.plugins.webviewflutter.Constants.ACTION_REQUEST_CAMERA_PERMISSION_FINISHED;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ALLOW_MULTIPLE_FILES;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_FILE_URIS;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_IMAGE_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_VIDEO_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TITLE;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ACCEPT_TYPES;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.webkit.ValueCallback;
import androidx.core.content.ContextCompat;
import java.util.Arrays;
public class FileChooserLauncher extends BroadcastReceiver {
private Context context;
private String title;
private boolean allowMultipleFiles;
private boolean videoAcceptable;
private boolean imageAcceptable;
private ValueCallback<Uri[]> filePathCallback;
private String[] acceptTypes;
public FileChooserLauncher(
Context context,
boolean allowMultipleFiles,
ValueCallback<Uri[]> filePathCallback,
String[] acceptTypes) {
this.context = context;
this.allowMultipleFiles = allowMultipleFiles;
this.filePathCallback = filePathCallback;
this.acceptTypes = acceptTypes;
if (acceptTypes.length == 0 || (acceptTypes.length == 1 && acceptTypes[0].length() == 0)) {
// acceptTypes empty -> accept anything
imageAcceptable = true;
videoAcceptable = true;
}
else {
for (String acceptType : acceptTypes) {
if (acceptType.startsWith("image/")) {
imageAcceptable = true;
}
else if (acceptType.startsWith("video/")) {
videoAcceptable = true;
}
}
}
if (imageAcceptable && !videoAcceptable) {
title = context.getResources().getString(R.string.webview_image_chooser_title);
}
else if (videoAcceptable && !imageAcceptable) {
title = context.getResources().getString(R.string.webview_video_chooser_title);
}
else {
title = context.getResources().getString(R.string.webview_file_chooser_title);
}
}
private boolean canCameraProduceAcceptableType() {
return imageAcceptable || videoAcceptable;
}
private boolean hasCameraPermission() {
return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED;
}
public void start() {
if (!canCameraProduceAcceptableType() || hasCameraPermission()) {
showFileChooser();
} else {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED);
context.registerReceiver(this, intentFilter);
Intent intent = new Intent(context, RequestCameraPermissionActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
private void showFileChooser() {
IntentFilter intentFilter = new IntentFilter(ACTION_FILE_CHOOSER_FINISHED);
context.registerReceiver(this, intentFilter);
Intent intent = new Intent(context, FileChooserActivity.class);
intent.putExtra(EXTRA_TITLE, title);
intent.putExtra(EXTRA_ACCEPT_TYPES, acceptTypes);
intent.putExtra(EXTRA_SHOW_IMAGE_OPTION, imageAcceptable && hasCameraPermission());
intent.putExtra(EXTRA_SHOW_VIDEO_OPTION, videoAcceptable && hasCameraPermission());
intent.putExtra(EXTRA_ALLOW_MULTIPLE_FILES, allowMultipleFiles);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED)) {
context.unregisterReceiver(this);
showFileChooser();
} else if (intent.getAction().equals(ACTION_FILE_CHOOSER_FINISHED)) {
String[] uriStrings = intent.getStringArrayExtra(EXTRA_FILE_URIS);
Uri[] result = null;
if (uriStrings != null) {
int uriStringCount = uriStrings.length;
result = new Uri[uriStringCount];
for (int i = 0; i < uriStringCount; i++) {
result[i] = Uri.parse(uriStrings[i]);
}
}
filePathCallback.onReceiveValue(result);
context.unregisterReceiver(this);
filePathCallback = null;
}
}
}
// 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.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.webkit.ValueCallback;
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.Arrays;
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 WebView webView;
private final MethodChannel methodChannel;
private final FlutterWebViewClient flutterWebViewClient;
private final Handler platformThreadHandler;
// 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,
FileChooserParams fileChooserParams) {
// info as of 2021-03-08:
// don't use fileChooserParams.getTitle() as it is (always? on Mi 9T Pro Android 10 at least) null
// don't use fileChooserParams.isCaptureEnabled() as it is (always? on Mi 9T Pro Android 10 at least) false, even when the file upload allows images or any file
final Context context = webView.getContext();
final boolean allowMultipleFiles = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;
new FileChooserLauncher(context, allowMultipleFiles, filePathCallback, fileChooserParams.getAcceptTypes()).start();
return true;
}
@Override
public void onProgressChanged(WebView view, int progress) {
flutterWebViewClient.onLoadingProgress(progress);
}
}
@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);
Boolean usesHybridComposition = (Boolean) params.get("usesHybridComposition");
webView =
(usesHybridComposition)
? new WebView(context)
: new InputAwareWebView(context, containerView);
displayListenerProxy.onPostWebViewInitialization(displayManager);
platformThreadHandler = new Handler(context.getMainLooper());
// Allow local storage.
webView.getSettings().setDomStorageEnabled(true);
webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(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() {
if (webView instanceof InputAwareWebView) {
((InputAwareWebView) 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() {
if (webView instanceof InputAwareWebView) {
((InputAwareWebView) 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) {
if (webView instanceof InputAwareWebView) {
((InputAwareWebView) 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() {
if (webView instanceof InputAwareWebView) {
((InputAwareWebView) 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 "hasProgressTracking":
flutterWebViewClient.hasProgressTracking = (boolean) settings.get(key);
break;
case "gestureNavigationEnabled":
break;
case "userAgent":
updateUserAgent((String) settings.get(key));
break;
case "allowsInlineMediaPlayback":
// no-op inline media playback is always allowed on Android.
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);
if (webView instanceof InputAwareWebView) {
((InputAwareWebView) webView).dispose();
}
webView.destroy();
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="webview_file_chooser_title">Choose a file</string>
<string name="webview_image_chooser_title">Choose an image</string>
<string name="webview_video_chooser_title">Choose a video</string>
</resources>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment