44import android .net .Uri ;
55import android .annotation .TargetApi ;
66import android .app .Activity ;
7+ import android .content .Context ;
78import android .os .Build ;
89import android .view .KeyEvent ;
910import android .view .View ;
1516import android .webkit .WebSettings ;
1617import android .webkit .WebView ;
1718import 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 ;
1926import java .util .HashMap ;
2027import java .util .Map ;
28+ import java .io .File ;
29+ import java .util .Date ;
30+ import java .io .IOException ;
31+ import java .text .SimpleDateFormat ;
2132
2233import io .flutter .plugin .common .MethodCall ;
2334import 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 >() {
0 commit comments