Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ android {
disable 'InvalidPackage'
}
}
dependencies {
implementation group: 'androidx.appcompat', name: 'appcompat', version: '1.0.0'
}
13 changes: 13 additions & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.flutter_webview_plugin">
<application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="android:authorities">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,20 @@
public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.ActivityResultListener {
private Activity activity;
private WebviewManager webViewManager;
private Context context;
static MethodChannel channel;
private static final String CHANNEL_NAME = "flutter_webview_plugin";

public static void registerWith(PluginRegistry.Registrar registrar) {
channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME);
final FlutterWebviewPlugin instance = new FlutterWebviewPlugin(registrar.activity());
final FlutterWebviewPlugin instance = new FlutterWebviewPlugin(registrar.activity(),registrar.activeContext());
registrar.addActivityResultListener(instance);
channel.setMethodCallHandler(instance);
}

private FlutterWebviewPlugin(Activity activity) {
private FlutterWebviewPlugin(Activity activity, Context context) {
this.activity = activity;
this.context = context;
}

@Override
Expand Down Expand Up @@ -102,7 +104,7 @@ private void openUrl(MethodCall call, MethodChannel.Result result) {
boolean geolocationEnabled = call.argument("geolocationEnabled");

if (webViewManager == null || webViewManager.closed == true) {
webViewManager = new WebviewManager(activity);
webViewManager = new WebviewManager(activity, context);
}

FrameLayout.LayoutParams params = buildLayoutParams(call);
Expand Down
154 changes: 143 additions & 11 deletions android/src/main/java/com/flutter_webview_plugin/WebviewManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.net.Uri;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.view.KeyEvent;
import android.view.View;
Expand All @@ -15,9 +16,19 @@
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.FrameLayout;
import android.provider.MediaStore;
import androidx.core.content.FileProvider;
import android.database.Cursor;
import android.provider.OpenableColumns;

import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.io.File;
import java.util.Date;
import java.io.IOException;
import java.text.SimpleDateFormat;

import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
Expand All @@ -33,6 +44,15 @@ class WebviewManager {
private ValueCallback<Uri> mUploadMessage;
private ValueCallback<Uri[]> mUploadMessageArray;
private final static int FILECHOOSER_RESULTCODE=1;
private Uri fileUri;
private Uri videoUri;

private long getFileSize(Uri fileUri) {
Cursor returnCursor = context.getContentResolver().query(fileUri, null, null, null, null);
returnCursor.moveToFirst();
int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
return returnCursor.getLong(sizeIndex);
}

@TargetApi(7)
class ResultHandler {
Expand All @@ -41,10 +61,13 @@ public boolean handleResult(int requestCode, int resultCode, Intent intent){
if(Build.VERSION.SDK_INT >= 21){
if(requestCode == FILECHOOSER_RESULTCODE){
Uri[] results = null;
if(resultCode == Activity.RESULT_OK && intent != null){
String dataString = intent.getDataString();
if(dataString != null){
results = new Uri[]{ Uri.parse(dataString) };
if (resultCode == Activity.RESULT_OK) {
if (fileUri != null && getFileSize(fileUri) > 0) {
results = new Uri[] { fileUri };
} else if (videoUri != null && getFileSize(videoUri) > 0) {
results = new Uri[] { videoUri };
} else if (intent != null) {
results = getSelectedFiles(intent);
}
}
if(mUploadMessageArray != null){
Expand All @@ -70,15 +93,37 @@ public boolean handleResult(int requestCode, int resultCode, Intent intent){
}
}

private Uri[] getSelectedFiles(Intent data) {
// we have one files selected
if (data.getData() != null) {
String dataString = data.getDataString();
if(dataString != null){
return new Uri[]{ Uri.parse(dataString) };
}
}
// we have multiple files selected
if (data.getClipData() != null) {
final int numSelectedFiles = data.getClipData().getItemCount();
Uri[] result = new Uri[numSelectedFiles];
for (int i = 0; i < numSelectedFiles; i++) {
result[i] = data.getClipData().getItemAt(i).getUri();
}
return result;
}
return null;
}

boolean closed = false;
WebView webView;
Activity activity;
BrowserClient webViewClient;
ResultHandler resultHandler;
Context context;

WebviewManager(final Activity activity) {
WebviewManager(final Activity activity, final Context context) {
this.webView = new ObservableWebView(activity);
this.activity = activity;
this.context = context;
this.resultHandler = new ResultHandler();
webViewClient = new BrowserClient();
webView.setOnKeyListener(new View.OnKeyListener() {
Expand Down Expand Up @@ -157,22 +202,109 @@ public boolean onShowFileChooser(
}
mUploadMessageArray = filePathCallback;

Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType("*/*");
Intent[] intentArray;
intentArray = new Intent[0];
final String[] acceptTypes = getSafeAcceptedTypes(fileChooserParams);
List<Intent> intentList = new ArrayList<Intent>();
fileUri = null;
videoUri = null;
if (acceptsImages(acceptTypes)) {
Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
fileUri = getOutputFilename(MediaStore.ACTION_IMAGE_CAPTURE);
takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
intentList.add(takePhotoIntent);
}
if (acceptsVideo(acceptTypes)) {
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
videoUri = getOutputFilename(MediaStore.ACTION_VIDEO_CAPTURE);
takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, videoUri);
intentList.add(takeVideoIntent);
}
Intent contentSelectionIntent;
if (Build.VERSION.SDK_INT >= 21) {
final boolean allowMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;
contentSelectionIntent = fileChooserParams.createIntent();
contentSelectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple);
} else {
contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType("*/*");
}
Intent[] intentArray = intentList.toArray(new Intent[intentList.size()]);

Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
activity.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
return true;
}
});
}

private Uri getOutputFilename(String intentType) {
String prefix = "";
String suffix = "";

if (intentType == MediaStore.ACTION_IMAGE_CAPTURE) {
prefix = "image-";
suffix = ".jpg";
} else if (intentType == MediaStore.ACTION_VIDEO_CAPTURE) {
prefix = "video-";
suffix = ".mp4";
}

String packageName = context.getPackageName();
File capturedFile = null;
try {
capturedFile = createCapturedFile(prefix, suffix);
} catch (IOException e) {
e.printStackTrace();
}
return FileProvider.getUriForFile(context, packageName + ".fileprovider", capturedFile);
}

private File createCapturedFile(String prefix, String suffix) throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = prefix + "_" + timeStamp;
File storageDir = context.getExternalFilesDir(null);
return File.createTempFile(imageFileName, suffix, storageDir);
}

private Boolean acceptsImages(String[] types) {
return isArrayEmpty(types) || arrayContainsString(types, "image");
}

private Boolean acceptsVideo(String[] types) {
return isArrayEmpty(types) || arrayContainsString(types, "video");
}

private Boolean arrayContainsString(String[] array, String pattern) {
for (String content : array) {
if (content.contains(pattern)) {
return true;
}
}
return false;
}

private Boolean isArrayEmpty(String[] arr) {
// when our array returned from getAcceptTypes() has no values set from the
// webview
// i.e. <input type="file" />, without any "accept" attr
// will be an array with one empty string element, afaik
return arr.length == 0 || (arr.length == 1 && arr[0].length() == 0);
}

private String[] getSafeAcceptedTypes(WebChromeClient.FileChooserParams params) {

// the getAcceptTypes() is available only in api 21+
// for lower level, we ignore it
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return params.getAcceptTypes();
}

final String[] EMPTY = {};
return EMPTY;
}

private void clearCookies() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().removeAllCookies(new ValueCallback<Boolean>() {
Expand Down
20 changes: 20 additions & 0 deletions android/src/main/res/xml/filepaths.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<paths>
<external-path
name="external-path"
path="."/>
<external-cache-path
name="external-cache-path"
path="."/>
<external-files-path
name="external-files-path"
path="."/>
<files-path
name="files_path"
path="."/>
<cache-path
name="cache-path"
path="."/>
<root-path
name="name"
path="."/>
</paths>