Переглянути джерело

Merge pull request #359 from islxyqwe/master

Ability to choose from camera or gallery when using <input type="file" />
Rafal Wachol 6 роки тому
батько
коміт
17d13e0209

+ 3 - 0
android/build.gradle

@@ -45,3 +45,6 @@ android {
         disable 'InvalidPackage'
     }
 }
+dependencies {
+    implementation group: 'androidx.appcompat', name: 'appcompat', version: '1.0.0'
+}

+ 13 - 0
android/src/main/AndroidManifest.xml

@@ -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>

+ 5 - 3
android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java

@@ -24,18 +24,20 @@ import io.flutter.plugin.common.PluginRegistry;
 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 @@ public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.A
         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);

+ 143 - 11
android/src/main/java/com/flutter_webview_plugin/WebviewManager.java

@@ -4,6 +4,7 @@ import android.content.Intent;
 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.WebChromeClient;
 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<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 {
@@ -41,10 +61,13 @@ class WebviewManager {
             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 @@ class WebviewManager {
         }
     }
 
+    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 @@ class WebviewManager {
                 }
                 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;
@@ -173,6 +239,72 @@ class WebviewManager {
         });
     }
 
+    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>() {

+ 20 - 0
android/src/main/res/xml/filepaths.xml

@@ -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>