diff --git a/android/build.gradle b/android/build.gradle index 8a96c2d6..da473746 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -45,3 +45,6 @@ android { disable 'InvalidPackage' } } +dependencies { + implementation group: 'androidx.appcompat', name: 'appcompat', version: '1.0.0' +} diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index b3b8fc6b..a9e05ce2 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,3 +1,16 @@ + + + + + diff --git a/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java b/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java index 65b7e947..53c51434 100644 --- a/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java +++ b/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java @@ -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 @@ -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); diff --git a/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java b/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java index 1d167e6f..d18a8653 100644 --- a/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java +++ b/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java @@ -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; @@ -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; @@ -33,6 +44,15 @@ class WebviewManager { private ValueCallback mUploadMessage; private ValueCallback 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 { @@ -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){ @@ -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() { @@ -157,15 +202,36 @@ 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 intentList = new ArrayList(); + 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; @@ -173,6 +239,72 @@ public boolean onShowFileChooser( }); } + 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. , 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() { diff --git a/android/src/main/res/xml/filepaths.xml b/android/src/main/res/xml/filepaths.xml new file mode 100644 index 00000000..43f25539 --- /dev/null +++ b/android/src/main/res/xml/filepaths.xml @@ -0,0 +1,20 @@ + + + + + + + +