Browse Source

fix(android): Fix several Android file upload issues (#1302 by @hojason117)

[skip ci]
Jason Chia-Hsien Ho 4 years ago
parent
commit
89886c820d
No account linked to committer's email address

+ 1
- 2
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java View File

971
     public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
971
     public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
972
       String[] acceptTypes = fileChooserParams.getAcceptTypes();
972
       String[] acceptTypes = fileChooserParams.getAcceptTypes();
973
       boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
973
       boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
974
-      Intent intent = fileChooserParams.createIntent();
975
-      return getModule(mReactContext).startPhotoPickerIntent(filePathCallback, intent, acceptTypes, allowMultiple);
974
+      return getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptTypes, allowMultiple);
976
     }
975
     }
977
 
976
 
978
     @Override
977
     @Override

+ 143
- 63
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewModule.java View File

11
 import android.os.Environment;
11
 import android.os.Environment;
12
 import android.os.Parcelable;
12
 import android.os.Parcelable;
13
 import android.provider.MediaStore;
13
 import android.provider.MediaStore;
14
+
14
 import androidx.annotation.RequiresApi;
15
 import androidx.annotation.RequiresApi;
15
 import androidx.core.content.ContextCompat;
16
 import androidx.core.content.ContextCompat;
16
 import androidx.core.content.FileProvider;
17
 import androidx.core.content.FileProvider;
18
+
17
 import android.util.Log;
19
 import android.util.Log;
18
 import android.webkit.MimeTypeMap;
20
 import android.webkit.MimeTypeMap;
19
 import android.webkit.ValueCallback;
21
 import android.webkit.ValueCallback;
42
   private static final int PICKER = 1;
44
   private static final int PICKER = 1;
43
   private static final int PICKER_LEGACY = 3;
45
   private static final int PICKER_LEGACY = 3;
44
   private static final int FILE_DOWNLOAD_PERMISSION_REQUEST = 1;
46
   private static final int FILE_DOWNLOAD_PERMISSION_REQUEST = 1;
45
-  final String DEFAULT_MIME_TYPES = "*/*";
46
   private ValueCallback<Uri> filePathCallbackLegacy;
47
   private ValueCallback<Uri> filePathCallbackLegacy;
47
   private ValueCallback<Uri[]> filePathCallback;
48
   private ValueCallback<Uri[]> filePathCallback;
48
-  private Uri outputFileUri;
49
+  private File outputImage;
50
+  private File outputVideo;
49
   private DownloadManager.Request downloadRequest;
51
   private DownloadManager.Request downloadRequest;
52
+
53
+  private enum MimeType {
54
+    DEFAULT("*/*"),
55
+    IMAGE("image"),
56
+    VIDEO("video");
57
+
58
+    private final String value;
59
+
60
+    MimeType(String value) {
61
+      this.value = value;
62
+    }
63
+  }
64
+
50
   private PermissionListener webviewFileDownloaderPermissionListener = new PermissionListener() {
65
   private PermissionListener webviewFileDownloaderPermissionListener = new PermissionListener() {
51
     @Override
66
     @Override
52
     public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
67
     public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
96
       return;
111
       return;
97
     }
112
     }
98
 
113
 
114
+    boolean imageTaken = false;
115
+    boolean videoTaken = false;
116
+
117
+    if (outputImage != null && outputImage.length() > 0) {
118
+      imageTaken = true;
119
+    }
120
+    if (outputVideo != null && outputVideo.length() > 0) {
121
+      videoTaken = true;
122
+    }
123
+
99
     // based off of which button was pressed, we get an activity result and a file
124
     // based off of which button was pressed, we get an activity result and a file
100
     // the camera activity doesn't properly return the filename* (I think?) so we use
125
     // the camera activity doesn't properly return the filename* (I think?) so we use
101
     // this filename instead
126
     // this filename instead
106
             filePathCallback.onReceiveValue(null);
131
             filePathCallback.onReceiveValue(null);
107
           }
132
           }
108
         } else {
133
         } else {
109
-          Uri result[] = this.getSelectedFiles(data, resultCode);
110
-          if (result != null) {
111
-            filePathCallback.onReceiveValue(result);
134
+          if (imageTaken) {
135
+            filePathCallback.onReceiveValue(new Uri[]{getOutputUri(outputImage)});
136
+          } else if (videoTaken) {
137
+            filePathCallback.onReceiveValue(new Uri[]{getOutputUri(outputVideo)});
112
           } else {
138
           } else {
113
-            filePathCallback.onReceiveValue(new Uri[]{outputFileUri});
139
+            filePathCallback.onReceiveValue(this.getSelectedFiles(data, resultCode));
114
           }
140
           }
115
         }
141
         }
116
         break;
142
         break;
117
       case PICKER_LEGACY:
143
       case PICKER_LEGACY:
118
-        Uri result = resultCode != Activity.RESULT_OK ? null : data == null ? outputFileUri : data.getData();
119
-        filePathCallbackLegacy.onReceiveValue(result);
144
+        if (resultCode != RESULT_OK) {
145
+          filePathCallbackLegacy.onReceiveValue(null);
146
+        } else {
147
+          if (imageTaken) {
148
+            filePathCallbackLegacy.onReceiveValue(getOutputUri(outputImage));
149
+          } else if (videoTaken) {
150
+            filePathCallbackLegacy.onReceiveValue(getOutputUri(outputVideo));
151
+          } else {
152
+            filePathCallbackLegacy.onReceiveValue(data.getData());
153
+          }
154
+        }
120
         break;
155
         break;
121
 
156
 
122
     }
157
     }
158
+
159
+    if (outputImage != null && !imageTaken) {
160
+      outputImage.delete();
161
+    }
162
+    if (outputVideo != null && !videoTaken) {
163
+      outputVideo.delete();
164
+    }
165
+
123
     filePathCallback = null;
166
     filePathCallback = null;
124
     filePathCallbackLegacy = null;
167
     filePathCallbackLegacy = null;
125
-    outputFileUri = null;
168
+    outputImage = null;
169
+    outputVideo = null;
126
   }
170
   }
127
 
171
 
128
   public void onNewIntent(Intent intent) {
172
   public void onNewIntent(Intent intent) {
133
       return null;
177
       return null;
134
     }
178
     }
135
 
179
 
136
-    // we have one file selected
137
-    if (data.getData() != null) {
138
-      if (resultCode == RESULT_OK && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
139
-        return WebChromeClient.FileChooserParams.parseResult(resultCode, data);
140
-      } else {
141
-        return null;
142
-      }
143
-    }
144
-
145
     // we have multiple files selected
180
     // we have multiple files selected
146
     if (data.getClipData() != null) {
181
     if (data.getClipData() != null) {
147
       final int numSelectedFiles = data.getClipData().getItemCount();
182
       final int numSelectedFiles = data.getClipData().getItemCount();
151
       }
186
       }
152
       return result;
187
       return result;
153
     }
188
     }
189
+
190
+    // we have one file selected
191
+    if (data.getData() != null && resultCode == RESULT_OK && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
192
+      return WebChromeClient.FileChooserParams.parseResult(resultCode, data);
193
+    }
194
+
154
     return null;
195
     return null;
155
   }
196
   }
156
 
197
 
162
 
203
 
163
     ArrayList<Parcelable> extraIntents = new ArrayList<>();
204
     ArrayList<Parcelable> extraIntents = new ArrayList<>();
164
     if (acceptsImages(acceptType)) {
205
     if (acceptsImages(acceptType)) {
165
-      extraIntents.add(getPhotoIntent());
206
+      Intent photoIntent = getPhotoIntent();
207
+      if (photoIntent != null) {
208
+        extraIntents.add(photoIntent);
209
+      }
166
     }
210
     }
167
     if (acceptsVideo(acceptType)) {
211
     if (acceptsVideo(acceptType)) {
168
-      extraIntents.add(getVideoIntent());
212
+      Intent videoIntent = getVideoIntent();
213
+      if (videoIntent != null) {
214
+        extraIntents.add(videoIntent);
215
+      }
169
     }
216
     }
170
     chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
217
     chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
171
 
218
 
177
   }
224
   }
178
 
225
 
179
   @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
226
   @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
180
-  public boolean startPhotoPickerIntent(final ValueCallback<Uri[]> callback, final Intent intent, final String[] acceptTypes, final boolean allowMultiple) {
227
+  public boolean startPhotoPickerIntent(final ValueCallback<Uri[]> callback, final String[] acceptTypes, final boolean allowMultiple) {
181
     filePathCallback = callback;
228
     filePathCallback = callback;
182
 
229
 
183
     ArrayList<Parcelable> extraIntents = new ArrayList<>();
230
     ArrayList<Parcelable> extraIntents = new ArrayList<>();
184
-    if (! needsCameraPermission()) {
231
+    if (!needsCameraPermission()) {
185
       if (acceptsImages(acceptTypes)) {
232
       if (acceptsImages(acceptTypes)) {
186
-        extraIntents.add(getPhotoIntent());
233
+        Intent photoIntent = getPhotoIntent();
234
+        if (photoIntent != null) {
235
+          extraIntents.add(photoIntent);
236
+        }
187
       }
237
       }
188
       if (acceptsVideo(acceptTypes)) {
238
       if (acceptsVideo(acceptTypes)) {
189
-        extraIntents.add(getVideoIntent());
239
+        Intent videoIntent = getVideoIntent();
240
+        if (videoIntent != null) {
241
+          extraIntents.add(videoIntent);
242
+        }
190
       }
243
       }
191
     }
244
     }
192
 
245
 
254
   }
307
   }
255
 
308
 
256
   private Intent getPhotoIntent() {
309
   private Intent getPhotoIntent() {
257
-    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
258
-    outputFileUri = getOutputUri(MediaStore.ACTION_IMAGE_CAPTURE);
259
-    intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
310
+    Intent intent = null;
311
+
312
+    try {
313
+      outputImage = getCapturedFile(MimeType.IMAGE);
314
+      Uri outputImageUri = getOutputUri(outputImage);
315
+      intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
316
+      intent.putExtra(MediaStore.EXTRA_OUTPUT, outputImageUri);
317
+    } catch (IOException | IllegalArgumentException e) {
318
+      Log.e("CREATE FILE", "Error occurred while creating the File", e);
319
+      e.printStackTrace();
320
+    }
321
+
260
     return intent;
322
     return intent;
261
   }
323
   }
262
 
324
 
263
   private Intent getVideoIntent() {
325
   private Intent getVideoIntent() {
264
-    Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
265
-    outputFileUri = getOutputUri(MediaStore.ACTION_VIDEO_CAPTURE);
266
-    intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
326
+    Intent intent = null;
327
+
328
+    try {
329
+      outputVideo = getCapturedFile(MimeType.VIDEO);
330
+      Uri outputVideoUri = getOutputUri(outputVideo);
331
+      intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
332
+      intent.putExtra(MediaStore.EXTRA_OUTPUT, outputVideoUri);
333
+    } catch (IOException | IllegalArgumentException e) {
334
+      Log.e("CREATE FILE", "Error occurred while creating the File", e);
335
+      e.printStackTrace();
336
+    }
337
+    
267
     return intent;
338
     return intent;
268
   }
339
   }
269
 
340
 
270
   private Intent getFileChooserIntent(String acceptTypes) {
341
   private Intent getFileChooserIntent(String acceptTypes) {
271
     String _acceptTypes = acceptTypes;
342
     String _acceptTypes = acceptTypes;
272
     if (acceptTypes.isEmpty()) {
343
     if (acceptTypes.isEmpty()) {
273
-      _acceptTypes = DEFAULT_MIME_TYPES;
344
+      _acceptTypes = MimeType.DEFAULT.value;
274
     }
345
     }
275
     if (acceptTypes.matches("\\.\\w+")) {
346
     if (acceptTypes.matches("\\.\\w+")) {
276
       _acceptTypes = getMimeTypeFromExtension(acceptTypes.replace(".", ""));
347
       _acceptTypes = getMimeTypeFromExtension(acceptTypes.replace(".", ""));
284
   private Intent getFileChooserIntent(String[] acceptTypes, boolean allowMultiple) {
355
   private Intent getFileChooserIntent(String[] acceptTypes, boolean allowMultiple) {
285
     Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
356
     Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
286
     intent.addCategory(Intent.CATEGORY_OPENABLE);
357
     intent.addCategory(Intent.CATEGORY_OPENABLE);
287
-    intent.setType("*/*");
358
+    intent.setType(MimeType.DEFAULT.value);
288
     intent.putExtra(Intent.EXTRA_MIME_TYPES, getAcceptedMimeType(acceptTypes));
359
     intent.putExtra(Intent.EXTRA_MIME_TYPES, getAcceptedMimeType(acceptTypes));
289
     intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple);
360
     intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple);
290
     return intent;
361
     return intent;
295
     if (types.matches("\\.\\w+")) {
366
     if (types.matches("\\.\\w+")) {
296
       mimeType = getMimeTypeFromExtension(types.replace(".", ""));
367
       mimeType = getMimeTypeFromExtension(types.replace(".", ""));
297
     }
368
     }
298
-    return mimeType.isEmpty() || mimeType.toLowerCase().contains("image");
369
+    return mimeType.isEmpty() || mimeType.toLowerCase().contains(MimeType.IMAGE.value);
299
   }
370
   }
300
 
371
 
301
   private Boolean acceptsImages(String[] types) {
372
   private Boolean acceptsImages(String[] types) {
302
     String[] mimeTypes = getAcceptedMimeType(types);
373
     String[] mimeTypes = getAcceptedMimeType(types);
303
-    return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "image");
374
+    return arrayContainsString(mimeTypes, MimeType.DEFAULT.value) || arrayContainsString(mimeTypes, MimeType.IMAGE.value);
304
   }
375
   }
305
 
376
 
306
   private Boolean acceptsVideo(String types) {
377
   private Boolean acceptsVideo(String types) {
378
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
379
+      return false;
380
+    }
381
+
307
     String mimeType = types;
382
     String mimeType = types;
308
     if (types.matches("\\.\\w+")) {
383
     if (types.matches("\\.\\w+")) {
309
       mimeType = getMimeTypeFromExtension(types.replace(".", ""));
384
       mimeType = getMimeTypeFromExtension(types.replace(".", ""));
310
     }
385
     }
311
-    return mimeType.isEmpty() || mimeType.toLowerCase().contains("video");
386
+    return mimeType.isEmpty() || mimeType.toLowerCase().contains(MimeType.VIDEO.value);
312
   }
387
   }
313
 
388
 
314
   private Boolean acceptsVideo(String[] types) {
389
   private Boolean acceptsVideo(String[] types) {
390
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
391
+      return false;
392
+    }
393
+
315
     String[] mimeTypes = getAcceptedMimeType(types);
394
     String[] mimeTypes = getAcceptedMimeType(types);
316
-    return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "video");
395
+    return arrayContainsString(mimeTypes, MimeType.DEFAULT.value) || arrayContainsString(mimeTypes, MimeType.VIDEO.value);
317
   }
396
   }
318
 
397
 
319
   private Boolean arrayContainsString(String[] array, String pattern) {
398
   private Boolean arrayContainsString(String[] array, String pattern) {
326
   }
405
   }
327
 
406
 
328
   private String[] getAcceptedMimeType(String[] types) {
407
   private String[] getAcceptedMimeType(String[] types) {
329
-    if (isArrayEmpty(types)) {
330
-      return new String[]{DEFAULT_MIME_TYPES};
408
+    if (noAcceptTypesSet(types)) {
409
+      return new String[]{MimeType.DEFAULT.value};
331
     }
410
     }
332
     String[] mimeTypes = new String[types.length];
411
     String[] mimeTypes = new String[types.length];
333
     for (int i = 0; i < types.length; i++) {
412
     for (int i = 0; i < types.length; i++) {
355
     return type;
434
     return type;
356
   }
435
   }
357
 
436
 
358
-  private Uri getOutputUri(String intentType) {
359
-    File capturedFile = null;
360
-    try {
361
-      capturedFile = getCapturedFile(intentType);
362
-    } catch (IOException e) {
363
-      Log.e("CREATE FILE", "Error occurred while creating the File", e);
364
-      e.printStackTrace();
365
-    }
366
-
437
+  private Uri getOutputUri(File capturedFile) {
367
     // for versions below 6.0 (23) we use the old File creation & permissions model
438
     // for versions below 6.0 (23) we use the old File creation & permissions model
368
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
439
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
369
       return Uri.fromFile(capturedFile);
440
       return Uri.fromFile(capturedFile);
374
     return FileProvider.getUriForFile(getReactApplicationContext(), packageName + ".fileprovider", capturedFile);
445
     return FileProvider.getUriForFile(getReactApplicationContext(), packageName + ".fileprovider", capturedFile);
375
   }
446
   }
376
 
447
 
377
-  private File getCapturedFile(String intentType) throws IOException {
448
+  private File getCapturedFile(MimeType mimeType) throws IOException {
378
     String prefix = "";
449
     String prefix = "";
379
     String suffix = "";
450
     String suffix = "";
380
     String dir = "";
451
     String dir = "";
381
-    String filename = "";
382
 
452
 
383
-    if (intentType.equals(MediaStore.ACTION_IMAGE_CAPTURE)) {
384
-      prefix = "image-";
385
-      suffix = ".jpg";
386
-      dir = Environment.DIRECTORY_PICTURES;
387
-    } else if (intentType.equals(MediaStore.ACTION_VIDEO_CAPTURE)) {
388
-      prefix = "video-";
389
-      suffix = ".mp4";
390
-      dir = Environment.DIRECTORY_MOVIES;
453
+    switch (mimeType) {
454
+      case IMAGE:
455
+        prefix = "image-";
456
+        suffix = ".jpg";
457
+        dir = Environment.DIRECTORY_PICTURES;
458
+        break;
459
+      case VIDEO:
460
+        prefix = "video-";
461
+        suffix = ".mp4";
462
+        dir = Environment.DIRECTORY_MOVIES;
463
+        break;
464
+
465
+      default:
466
+        break;
391
     }
467
     }
392
 
468
 
393
-    filename = prefix + String.valueOf(System.currentTimeMillis()) + suffix;
469
+    String filename = prefix + String.valueOf(System.currentTimeMillis()) + suffix;
470
+    File outputFile = null;
394
 
471
 
395
     // for versions below 6.0 (23) we use the old File creation & permissions model
472
     // for versions below 6.0 (23) we use the old File creation & permissions model
396
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
473
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
397
       // only this Directory works on all tested Android versions
474
       // only this Directory works on all tested Android versions
398
       // ctx.getExternalFilesDir(dir) was failing on Android 5.0 (sdk 21)
475
       // ctx.getExternalFilesDir(dir) was failing on Android 5.0 (sdk 21)
399
       File storageDir = Environment.getExternalStoragePublicDirectory(dir);
476
       File storageDir = Environment.getExternalStoragePublicDirectory(dir);
400
-      return new File(storageDir, filename);
477
+      outputFile = new File(storageDir, filename);
478
+    } else {
479
+      File storageDir = getReactApplicationContext().getExternalFilesDir(null);
480
+      outputFile = File.createTempFile(prefix, suffix, storageDir);
401
     }
481
     }
402
 
482
 
403
-    File storageDir = getReactApplicationContext().getExternalFilesDir(null);
404
-    return File.createTempFile(filename, suffix, storageDir);
483
+    return outputFile;
405
   }
484
   }
406
 
485
 
407
-  private Boolean isArrayEmpty(String[] arr) {
486
+  private Boolean noAcceptTypesSet(String[] types) {
408
     // when our array returned from getAcceptTypes() has no values set from the webview
487
     // when our array returned from getAcceptTypes() has no values set from the webview
409
     // i.e. <input type="file" />, without any "accept" attr
488
     // i.e. <input type="file" />, without any "accept" attr
410
     // will be an array with one empty string element, afaik
489
     // will be an array with one empty string element, afaik
411
-    return arr.length == 0 || (arr.length == 1 && arr[0] != null && arr[0].length() == 0);
490
+
491
+    return types.length == 0 || (types.length == 1 && types[0] != null && types[0].length() == 0);
412
   }
492
   }
413
 
493
 
414
   private PermissionAwareActivity getPermissionAwareActivity() {
494
   private PermissionAwareActivity getPermissionAwareActivity() {