Skip to content

Commit 17d13e0

Browse files
authored
Merge pull request #359 from islxyqwe/master
Ability to choose from camera or gallery when using <input type="file" />
2 parents 388c37f + b791da3 commit 17d13e0

File tree

5 files changed

+184
-14
lines changed

5 files changed

+184
-14
lines changed

android/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,6 @@ android {
4545
disable 'InvalidPackage'
4646
}
4747
}
48+
dependencies {
49+
implementation group: 'androidx.appcompat', name: 'appcompat', version: '1.0.0'
50+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2+
xmlns:tools="http://schemas.android.com/tools"
23
package="com.flutter_webview_plugin">
4+
<application>
5+
<provider
6+
android:name="androidx.core.content.FileProvider"
7+
android:authorities="${applicationId}.fileprovider"
8+
android:exported="false"
9+
android:grantUriPermissions="true"
10+
tools:replace="android:authorities">
11+
<meta-data
12+
android:name="android.support.FILE_PROVIDER_PATHS"
13+
android:resource="@xml/filepaths" />
14+
</provider>
15+
</application>
316
</manifest>

android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,20 @@
2424
public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.ActivityResultListener {
2525
private Activity activity;
2626
private WebviewManager webViewManager;
27+
private Context context;
2728
static MethodChannel channel;
2829
private static final String CHANNEL_NAME = "flutter_webview_plugin";
2930

3031
public static void registerWith(PluginRegistry.Registrar registrar) {
3132
channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME);
32-
final FlutterWebviewPlugin instance = new FlutterWebviewPlugin(registrar.activity());
33+
final FlutterWebviewPlugin instance = new FlutterWebviewPlugin(registrar.activity(),registrar.activeContext());
3334
registrar.addActivityResultListener(instance);
3435
channel.setMethodCallHandler(instance);
3536
}
3637

37-
private FlutterWebviewPlugin(Activity activity) {
38+
private FlutterWebviewPlugin(Activity activity, Context context) {
3839
this.activity = activity;
40+
this.context = context;
3941
}
4042

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

104106
if (webViewManager == null || webViewManager.closed == true) {
105-
webViewManager = new WebviewManager(activity);
107+
webViewManager = new WebviewManager(activity, context);
106108
}
107109

108110
FrameLayout.LayoutParams params = buildLayoutParams(call);

android/src/main/java/com/flutter_webview_plugin/WebviewManager.java

Lines changed: 143 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import android.net.Uri;
55
import android.annotation.TargetApi;
66
import android.app.Activity;
7+
import android.content.Context;
78
import android.os.Build;
89
import android.view.KeyEvent;
910
import android.view.View;
@@ -15,9 +16,19 @@
1516
import android.webkit.WebSettings;
1617
import android.webkit.WebView;
1718
import android.widget.FrameLayout;
19+
import android.provider.MediaStore;
20+
import androidx.core.content.FileProvider;
21+
import android.database.Cursor;
22+
import android.provider.OpenableColumns;
1823

24+
import java.util.List;
25+
import java.util.ArrayList;
1926
import java.util.HashMap;
2027
import java.util.Map;
28+
import java.io.File;
29+
import java.util.Date;
30+
import java.io.IOException;
31+
import java.text.SimpleDateFormat;
2132

2233
import io.flutter.plugin.common.MethodCall;
2334
import io.flutter.plugin.common.MethodChannel;
@@ -33,6 +44,15 @@ class WebviewManager {
3344
private ValueCallback<Uri> mUploadMessage;
3445
private ValueCallback<Uri[]> mUploadMessageArray;
3546
private final static int FILECHOOSER_RESULTCODE=1;
47+
private Uri fileUri;
48+
private Uri videoUri;
49+
50+
private long getFileSize(Uri fileUri) {
51+
Cursor returnCursor = context.getContentResolver().query(fileUri, null, null, null, null);
52+
returnCursor.moveToFirst();
53+
int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
54+
return returnCursor.getLong(sizeIndex);
55+
}
3656

3757
@TargetApi(7)
3858
class ResultHandler {
@@ -41,10 +61,13 @@ public boolean handleResult(int requestCode, int resultCode, Intent intent){
4161
if(Build.VERSION.SDK_INT >= 21){
4262
if(requestCode == FILECHOOSER_RESULTCODE){
4363
Uri[] results = null;
44-
if(resultCode == Activity.RESULT_OK && intent != null){
45-
String dataString = intent.getDataString();
46-
if(dataString != null){
47-
results = new Uri[]{ Uri.parse(dataString) };
64+
if (resultCode == Activity.RESULT_OK) {
65+
if (fileUri != null && getFileSize(fileUri) > 0) {
66+
results = new Uri[] { fileUri };
67+
} else if (videoUri != null && getFileSize(videoUri) > 0) {
68+
results = new Uri[] { videoUri };
69+
} else if (intent != null) {
70+
results = getSelectedFiles(intent);
4871
}
4972
}
5073
if(mUploadMessageArray != null){
@@ -70,15 +93,37 @@ public boolean handleResult(int requestCode, int resultCode, Intent intent){
7093
}
7194
}
7295

96+
private Uri[] getSelectedFiles(Intent data) {
97+
// we have one files selected
98+
if (data.getData() != null) {
99+
String dataString = data.getDataString();
100+
if(dataString != null){
101+
return new Uri[]{ Uri.parse(dataString) };
102+
}
103+
}
104+
// we have multiple files selected
105+
if (data.getClipData() != null) {
106+
final int numSelectedFiles = data.getClipData().getItemCount();
107+
Uri[] result = new Uri[numSelectedFiles];
108+
for (int i = 0; i < numSelectedFiles; i++) {
109+
result[i] = data.getClipData().getItemAt(i).getUri();
110+
}
111+
return result;
112+
}
113+
return null;
114+
}
115+
73116
boolean closed = false;
74117
WebView webView;
75118
Activity activity;
76119
BrowserClient webViewClient;
77120
ResultHandler resultHandler;
121+
Context context;
78122

79-
WebviewManager(final Activity activity) {
123+
WebviewManager(final Activity activity, final Context context) {
80124
this.webView = new ObservableWebView(activity);
81125
this.activity = activity;
126+
this.context = context;
82127
this.resultHandler = new ResultHandler();
83128
webViewClient = new BrowserClient();
84129
webView.setOnKeyListener(new View.OnKeyListener() {
@@ -157,22 +202,109 @@ public boolean onShowFileChooser(
157202
}
158203
mUploadMessageArray = filePathCallback;
159204

160-
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
161-
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
162-
contentSelectionIntent.setType("*/*");
163-
Intent[] intentArray;
164-
intentArray = new Intent[0];
205+
final String[] acceptTypes = getSafeAcceptedTypes(fileChooserParams);
206+
List<Intent> intentList = new ArrayList<Intent>();
207+
fileUri = null;
208+
videoUri = null;
209+
if (acceptsImages(acceptTypes)) {
210+
Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
211+
fileUri = getOutputFilename(MediaStore.ACTION_IMAGE_CAPTURE);
212+
takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
213+
intentList.add(takePhotoIntent);
214+
}
215+
if (acceptsVideo(acceptTypes)) {
216+
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
217+
videoUri = getOutputFilename(MediaStore.ACTION_VIDEO_CAPTURE);
218+
takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, videoUri);
219+
intentList.add(takeVideoIntent);
220+
}
221+
Intent contentSelectionIntent;
222+
if (Build.VERSION.SDK_INT >= 21) {
223+
final boolean allowMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;
224+
contentSelectionIntent = fileChooserParams.createIntent();
225+
contentSelectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple);
226+
} else {
227+
contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
228+
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
229+
contentSelectionIntent.setType("*/*");
230+
}
231+
Intent[] intentArray = intentList.toArray(new Intent[intentList.size()]);
165232

166233
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
167234
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
168-
chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
169235
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
170236
activity.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
171237
return true;
172238
}
173239
});
174240
}
175241

242+
private Uri getOutputFilename(String intentType) {
243+
String prefix = "";
244+
String suffix = "";
245+
246+
if (intentType == MediaStore.ACTION_IMAGE_CAPTURE) {
247+
prefix = "image-";
248+
suffix = ".jpg";
249+
} else if (intentType == MediaStore.ACTION_VIDEO_CAPTURE) {
250+
prefix = "video-";
251+
suffix = ".mp4";
252+
}
253+
254+
String packageName = context.getPackageName();
255+
File capturedFile = null;
256+
try {
257+
capturedFile = createCapturedFile(prefix, suffix);
258+
} catch (IOException e) {
259+
e.printStackTrace();
260+
}
261+
return FileProvider.getUriForFile(context, packageName + ".fileprovider", capturedFile);
262+
}
263+
264+
private File createCapturedFile(String prefix, String suffix) throws IOException {
265+
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
266+
String imageFileName = prefix + "_" + timeStamp;
267+
File storageDir = context.getExternalFilesDir(null);
268+
return File.createTempFile(imageFileName, suffix, storageDir);
269+
}
270+
271+
private Boolean acceptsImages(String[] types) {
272+
return isArrayEmpty(types) || arrayContainsString(types, "image");
273+
}
274+
275+
private Boolean acceptsVideo(String[] types) {
276+
return isArrayEmpty(types) || arrayContainsString(types, "video");
277+
}
278+
279+
private Boolean arrayContainsString(String[] array, String pattern) {
280+
for (String content : array) {
281+
if (content.contains(pattern)) {
282+
return true;
283+
}
284+
}
285+
return false;
286+
}
287+
288+
private Boolean isArrayEmpty(String[] arr) {
289+
// when our array returned from getAcceptTypes() has no values set from the
290+
// webview
291+
// i.e. <input type="file" />, without any "accept" attr
292+
// will be an array with one empty string element, afaik
293+
return arr.length == 0 || (arr.length == 1 && arr[0].length() == 0);
294+
}
295+
296+
private String[] getSafeAcceptedTypes(WebChromeClient.FileChooserParams params) {
297+
298+
// the getAcceptTypes() is available only in api 21+
299+
// for lower level, we ignore it
300+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
301+
return params.getAcceptTypes();
302+
}
303+
304+
final String[] EMPTY = {};
305+
return EMPTY;
306+
}
307+
176308
private void clearCookies() {
177309
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
178310
CookieManager.getInstance().removeAllCookies(new ValueCallback<Boolean>() {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<paths>
2+
<external-path
3+
name="external-path"
4+
path="."/>
5+
<external-cache-path
6+
name="external-cache-path"
7+
path="."/>
8+
<external-files-path
9+
name="external-files-path"
10+
path="."/>
11+
<files-path
12+
name="files_path"
13+
path="."/>
14+
<cache-path
15+
name="cache-path"
16+
path="."/>
17+
<root-path
18+
name="name"
19+
path="."/>
20+
</paths>

0 commit comments

Comments
 (0)