|
@@ -11,9 +11,11 @@ import android.os.Build;
|
11
|
11
|
import android.os.Environment;
|
12
|
12
|
import android.os.Parcelable;
|
13
|
13
|
import android.provider.MediaStore;
|
|
14
|
+
|
14
|
15
|
import androidx.annotation.RequiresApi;
|
15
|
16
|
import androidx.core.content.ContextCompat;
|
16
|
17
|
import androidx.core.content.FileProvider;
|
|
18
|
+
|
17
|
19
|
import android.util.Log;
|
18
|
20
|
import android.webkit.MimeTypeMap;
|
19
|
21
|
import android.webkit.ValueCallback;
|
|
@@ -42,11 +44,24 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
42
|
44
|
private static final int PICKER = 1;
|
43
|
45
|
private static final int PICKER_LEGACY = 3;
|
44
|
46
|
private static final int FILE_DOWNLOAD_PERMISSION_REQUEST = 1;
|
45
|
|
- final String DEFAULT_MIME_TYPES = "*/*";
|
46
|
47
|
private ValueCallback<Uri> filePathCallbackLegacy;
|
47
|
48
|
private ValueCallback<Uri[]> filePathCallback;
|
48
|
|
- private Uri outputFileUri;
|
|
49
|
+ private File outputImage;
|
|
50
|
+ private File outputVideo;
|
49
|
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
|
65
|
private PermissionListener webviewFileDownloaderPermissionListener = new PermissionListener() {
|
51
|
66
|
@Override
|
52
|
67
|
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
|
@@ -96,6 +111,16 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
96
|
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
|
124
|
// based off of which button was pressed, we get an activity result and a file
|
100
|
125
|
// the camera activity doesn't properly return the filename* (I think?) so we use
|
101
|
126
|
// this filename instead
|
|
@@ -106,23 +131,42 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
106
|
131
|
filePathCallback.onReceiveValue(null);
|
107
|
132
|
}
|
108
|
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
|
138
|
} else {
|
113
|
|
- filePathCallback.onReceiveValue(new Uri[]{outputFileUri});
|
|
139
|
+ filePathCallback.onReceiveValue(this.getSelectedFiles(data, resultCode));
|
114
|
140
|
}
|
115
|
141
|
}
|
116
|
142
|
break;
|
117
|
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
|
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
|
166
|
filePathCallback = null;
|
124
|
167
|
filePathCallbackLegacy = null;
|
125
|
|
- outputFileUri = null;
|
|
168
|
+ outputImage = null;
|
|
169
|
+ outputVideo = null;
|
126
|
170
|
}
|
127
|
171
|
|
128
|
172
|
public void onNewIntent(Intent intent) {
|
|
@@ -133,15 +177,6 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
133
|
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
|
180
|
// we have multiple files selected
|
146
|
181
|
if (data.getClipData() != null) {
|
147
|
182
|
final int numSelectedFiles = data.getClipData().getItemCount();
|
|
@@ -151,6 +186,12 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
151
|
186
|
}
|
152
|
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
|
195
|
return null;
|
155
|
196
|
}
|
156
|
197
|
|
|
@@ -162,10 +203,16 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
162
|
203
|
|
163
|
204
|
ArrayList<Parcelable> extraIntents = new ArrayList<>();
|
164
|
205
|
if (acceptsImages(acceptType)) {
|
165
|
|
- extraIntents.add(getPhotoIntent());
|
|
206
|
+ Intent photoIntent = getPhotoIntent();
|
|
207
|
+ if (photoIntent != null) {
|
|
208
|
+ extraIntents.add(photoIntent);
|
|
209
|
+ }
|
166
|
210
|
}
|
167
|
211
|
if (acceptsVideo(acceptType)) {
|
168
|
|
- extraIntents.add(getVideoIntent());
|
|
212
|
+ Intent videoIntent = getVideoIntent();
|
|
213
|
+ if (videoIntent != null) {
|
|
214
|
+ extraIntents.add(videoIntent);
|
|
215
|
+ }
|
169
|
216
|
}
|
170
|
217
|
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
|
171
|
218
|
|
|
@@ -177,16 +224,22 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
177
|
224
|
}
|
178
|
225
|
|
179
|
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
|
228
|
filePathCallback = callback;
|
182
|
229
|
|
183
|
230
|
ArrayList<Parcelable> extraIntents = new ArrayList<>();
|
184
|
|
- if (! needsCameraPermission()) {
|
|
231
|
+ if (!needsCameraPermission()) {
|
185
|
232
|
if (acceptsImages(acceptTypes)) {
|
186
|
|
- extraIntents.add(getPhotoIntent());
|
|
233
|
+ Intent photoIntent = getPhotoIntent();
|
|
234
|
+ if (photoIntent != null) {
|
|
235
|
+ extraIntents.add(photoIntent);
|
|
236
|
+ }
|
187
|
237
|
}
|
188
|
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,23 +307,41 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
254
|
307
|
}
|
255
|
308
|
|
256
|
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
|
322
|
return intent;
|
261
|
323
|
}
|
262
|
324
|
|
263
|
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
|
338
|
return intent;
|
268
|
339
|
}
|
269
|
340
|
|
270
|
341
|
private Intent getFileChooserIntent(String acceptTypes) {
|
271
|
342
|
String _acceptTypes = acceptTypes;
|
272
|
343
|
if (acceptTypes.isEmpty()) {
|
273
|
|
- _acceptTypes = DEFAULT_MIME_TYPES;
|
|
344
|
+ _acceptTypes = MimeType.DEFAULT.value;
|
274
|
345
|
}
|
275
|
346
|
if (acceptTypes.matches("\\.\\w+")) {
|
276
|
347
|
_acceptTypes = getMimeTypeFromExtension(acceptTypes.replace(".", ""));
|
|
@@ -284,7 +355,7 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
284
|
355
|
private Intent getFileChooserIntent(String[] acceptTypes, boolean allowMultiple) {
|
285
|
356
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
286
|
357
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
287
|
|
- intent.setType("*/*");
|
|
358
|
+ intent.setType(MimeType.DEFAULT.value);
|
288
|
359
|
intent.putExtra(Intent.EXTRA_MIME_TYPES, getAcceptedMimeType(acceptTypes));
|
289
|
360
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple);
|
290
|
361
|
return intent;
|
|
@@ -295,25 +366,33 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
295
|
366
|
if (types.matches("\\.\\w+")) {
|
296
|
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
|
372
|
private Boolean acceptsImages(String[] types) {
|
302
|
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
|
377
|
private Boolean acceptsVideo(String types) {
|
|
378
|
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
379
|
+ return false;
|
|
380
|
+ }
|
|
381
|
+
|
307
|
382
|
String mimeType = types;
|
308
|
383
|
if (types.matches("\\.\\w+")) {
|
309
|
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
|
389
|
private Boolean acceptsVideo(String[] types) {
|
|
390
|
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
391
|
+ return false;
|
|
392
|
+ }
|
|
393
|
+
|
315
|
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
|
398
|
private Boolean arrayContainsString(String[] array, String pattern) {
|
|
@@ -326,8 +405,8 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
326
|
405
|
}
|
327
|
406
|
|
328
|
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
|
411
|
String[] mimeTypes = new String[types.length];
|
333
|
412
|
for (int i = 0; i < types.length; i++) {
|
|
@@ -355,15 +434,7 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
355
|
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
|
438
|
// for versions below 6.0 (23) we use the old File creation & permissions model
|
368
|
439
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
369
|
440
|
return Uri.fromFile(capturedFile);
|
|
@@ -374,41 +445,50 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
374
|
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
|
449
|
String prefix = "";
|
379
|
450
|
String suffix = "";
|
380
|
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
|
472
|
// for versions below 6.0 (23) we use the old File creation & permissions model
|
396
|
473
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
397
|
474
|
// only this Directory works on all tested Android versions
|
398
|
475
|
// ctx.getExternalFilesDir(dir) was failing on Android 5.0 (sdk 21)
|
399
|
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
|
487
|
// when our array returned from getAcceptTypes() has no values set from the webview
|
409
|
488
|
// i.e. <input type="file" />, without any "accept" attr
|
410
|
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
|
494
|
private PermissionAwareActivity getPermissionAwareActivity() {
|