@connectedbits/react-native-image-picker 2.4.16-rc3 → 2.4.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +7 -12
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/imagepicker/ImagePickerModule.java +178 -195
- package/ios/ImagePickerManager.h +1 -1
- package/ios/ImagePickerManager.m +659 -443
- package/package.json +2 -2
- package/react-native-image-picker.podspec +2 -1
- package/android/.idea/.gitignore +0 -3
package/android/build.gradle
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
buildscript {
|
|
2
|
-
repositories {
|
|
3
|
-
google()
|
|
4
|
-
jcenter()
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
dependencies {
|
|
8
|
-
classpath 'com.android.tools.build:gradle:3.4.1'
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
1
|
def getExtOrDefault(name) {
|
|
13
2
|
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ReactNativeImagePicker_' + name]
|
|
14
3
|
}
|
|
@@ -20,6 +9,10 @@ def getExtOrIntegerDefault(name) {
|
|
|
20
9
|
apply plugin: 'com.android.library'
|
|
21
10
|
|
|
22
11
|
android {
|
|
12
|
+
// AGP 7.3+ wants the namespace here rather than in the manifest
|
|
13
|
+
if (project.android.hasProperty("namespace")) {
|
|
14
|
+
namespace "com.imagepicker"
|
|
15
|
+
}
|
|
23
16
|
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
|
|
24
17
|
buildToolsVersion getExtOrDefault('buildToolsVersion')
|
|
25
18
|
|
|
@@ -47,11 +40,13 @@ repositories {
|
|
|
47
40
|
url "$projectDir/../node_modules/jsc-android/dist"
|
|
48
41
|
}
|
|
49
42
|
google()
|
|
50
|
-
|
|
43
|
+
mavenCentral()
|
|
51
44
|
}
|
|
52
45
|
|
|
53
46
|
dependencies {
|
|
54
47
|
api "com.facebook.react:react-native:+" // From node_modules
|
|
48
|
+
// System Photo Picker contract (PickVisualMedia + PickVisualMediaRequest.Builder)
|
|
49
|
+
implementation "androidx.activity:activity:1.8.0"
|
|
55
50
|
|
|
56
51
|
testImplementation('org.robolectric:robolectric:4.3.1') {
|
|
57
52
|
// https://github.com/robolectric/robolectric/issues/5245
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
4
4
|
package="com.imagepicker"
|
|
5
5
|
>
|
|
6
|
+
<!-- Lets us read EXIF GPS from photos picked via the system Photo Picker when the provider allows it.
|
|
7
|
+
No storage permission is needed: the library uses the Photo Picker and captures go to app storage. -->
|
|
8
|
+
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
|
9
|
+
|
|
6
10
|
<application>
|
|
7
11
|
<provider
|
|
8
12
|
android:name="com.imagepicker.FileProvider"
|
|
@@ -6,7 +6,6 @@ import android.content.ActivityNotFoundException;
|
|
|
6
6
|
import android.content.Context;
|
|
7
7
|
import android.content.DialogInterface;
|
|
8
8
|
import android.content.Intent;
|
|
9
|
-
import android.content.pm.ResolveInfo;
|
|
10
9
|
import android.graphics.Bitmap;
|
|
11
10
|
import android.graphics.BitmapFactory;
|
|
12
11
|
import android.net.Uri;
|
|
@@ -14,7 +13,10 @@ import android.os.Build;
|
|
|
14
13
|
import android.os.Handler;
|
|
15
14
|
import android.provider.MediaStore;
|
|
16
15
|
import android.provider.Settings;
|
|
16
|
+
import androidx.activity.result.PickVisualMediaRequest;
|
|
17
|
+
import androidx.activity.result.contract.ActivityResultContracts;
|
|
17
18
|
import androidx.annotation.NonNull;
|
|
19
|
+
import androidx.annotation.Nullable;
|
|
18
20
|
import androidx.annotation.StyleRes;
|
|
19
21
|
import androidx.core.app.ActivityCompat;
|
|
20
22
|
import androidx.appcompat.app.AlertDialog;
|
|
@@ -47,7 +49,6 @@ import java.io.IOException;
|
|
|
47
49
|
import java.io.InputStream;
|
|
48
50
|
import java.io.UnsupportedEncodingException;
|
|
49
51
|
import java.lang.ref.WeakReference;
|
|
50
|
-
import java.util.List;
|
|
51
52
|
|
|
52
53
|
import com.facebook.react.modules.core.PermissionListener;
|
|
53
54
|
import com.facebook.react.modules.core.PermissionAwareActivity;
|
|
@@ -69,6 +70,8 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
69
70
|
public static final int REQUEST_LAUNCH_VIDEO_LIBRARY = 13003;
|
|
70
71
|
public static final int REQUEST_LAUNCH_VIDEO_CAPTURE = 13004;
|
|
71
72
|
public static final int REQUEST_PERMISSIONS_FOR_CAMERA = 14001;
|
|
73
|
+
// The system Photo Picker needs no storage permission. We only (opportunistically) ask for
|
|
74
|
+
// ACCESS_MEDIA_LOCATION so that EXIF GPS can be read when the provider is willing to hand it back.
|
|
72
75
|
public static final int REQUEST_PERMISSIONS_FOR_LIBRARY = 14002;
|
|
73
76
|
|
|
74
77
|
private final ReactApplicationContext reactContext;
|
|
@@ -91,6 +94,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
91
94
|
private int videoDurationLimit = 0;
|
|
92
95
|
|
|
93
96
|
private ResponseHelper responseHelper = new ResponseHelper();
|
|
97
|
+
private boolean mediaLocationRequested = false;
|
|
94
98
|
private PermissionListener listener = new PermissionListener()
|
|
95
99
|
{
|
|
96
100
|
public boolean onRequestPermissionsResult(final int requestCode,
|
|
@@ -109,24 +113,26 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
109
113
|
return false;
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
if (!permissionsGranted)
|
|
113
|
-
{
|
|
114
|
-
responseHelper.invokeError(permissionRequestCallback, "Permissions weren't granted");
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
116
|
switch (requestCode)
|
|
119
117
|
{
|
|
120
118
|
case REQUEST_PERMISSIONS_FOR_CAMERA:
|
|
119
|
+
if (!permissionsGranted)
|
|
120
|
+
{
|
|
121
|
+
responseHelper.invokeError(permissionRequestCallback, "Permissions weren't granted");
|
|
122
|
+
callback = null;
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
121
125
|
launchCamera(options, permissionRequestCallback);
|
|
122
|
-
|
|
126
|
+
return true;
|
|
123
127
|
|
|
124
128
|
case REQUEST_PERMISSIONS_FOR_LIBRARY:
|
|
129
|
+
// Open the picker whether or not media location was granted; without it we simply
|
|
130
|
+
// won't get EXIF GPS back.
|
|
131
|
+
mediaLocationRequested = true;
|
|
125
132
|
launchImageLibrary(options, permissionRequestCallback);
|
|
126
|
-
|
|
127
|
-
|
|
133
|
+
return true;
|
|
128
134
|
}
|
|
129
|
-
return
|
|
135
|
+
return false;
|
|
130
136
|
}
|
|
131
137
|
};
|
|
132
138
|
|
|
@@ -153,7 +159,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
153
159
|
@ReactMethod
|
|
154
160
|
public void showImagePicker(final ReadableMap options, final Callback callback) {
|
|
155
161
|
final ImagePickerModule self = this;
|
|
156
|
-
final Activity currentActivity = getCurrentActivity();
|
|
162
|
+
final Activity currentActivity = getReactApplicationContext().getCurrentActivity();
|
|
157
163
|
Handler handler = new Handler(this.reactContext.getMainLooper());
|
|
158
164
|
this.callback = callback;
|
|
159
165
|
this.options = options;
|
|
@@ -242,7 +248,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
242
248
|
return;
|
|
243
249
|
}
|
|
244
250
|
|
|
245
|
-
final Activity currentActivity = getCurrentActivity();
|
|
251
|
+
final Activity currentActivity = getReactApplicationContext().getCurrentActivity();
|
|
246
252
|
if (currentActivity == null)
|
|
247
253
|
{
|
|
248
254
|
responseHelper.invokeError(callback, "can't find current Activity");
|
|
@@ -296,16 +302,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
296
302
|
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraCaptureURI);
|
|
297
303
|
}
|
|
298
304
|
|
|
299
|
-
|
|
300
|
-
// grantUriPermission also needed for KITKAT,
|
|
301
|
-
// see https://code.google.com/p/android/issues/detail?id=76683
|
|
302
|
-
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
|
|
303
|
-
List<ResolveInfo> resInfoList = self.getContext().getPackageManager().queryIntentActivities(cameraIntent, PackageManager.MATCH_DEFAULT_ONLY);
|
|
304
|
-
for (ResolveInfo resolveInfo : resInfoList) {
|
|
305
|
-
String packageName = resolveInfo.activityInfo.packageName;
|
|
306
|
-
self.getContext().grantUriPermission(packageName, cameraCaptureURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
305
|
+
cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
309
306
|
|
|
310
307
|
try
|
|
311
308
|
{
|
|
@@ -330,7 +327,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
330
327
|
{
|
|
331
328
|
permissionRequestCallback = callback;
|
|
332
329
|
|
|
333
|
-
final Activity currentActivity = getCurrentActivity();
|
|
330
|
+
final Activity currentActivity = getReactApplicationContext().getCurrentActivity();
|
|
334
331
|
if (currentActivity == null) {
|
|
335
332
|
responseHelper.invokeError(callback, "can't find current Activity");
|
|
336
333
|
return;
|
|
@@ -343,47 +340,40 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
343
340
|
handler.post(new Runnable() {
|
|
344
341
|
@Override
|
|
345
342
|
public void run(){
|
|
346
|
-
if (
|
|
343
|
+
if (shouldRequestMediaLocation(currentActivity))
|
|
347
344
|
{
|
|
345
|
+
requestPermissions(currentActivity, new String[]{Manifest.permission.ACCESS_MEDIA_LOCATION}, REQUEST_PERMISSIONS_FOR_LIBRARY);
|
|
348
346
|
return;
|
|
349
347
|
}
|
|
348
|
+
mediaLocationRequested = false;
|
|
350
349
|
|
|
351
350
|
parseOptions(options);
|
|
352
351
|
|
|
353
|
-
|
|
354
|
-
|
|
352
|
+
// System Photo Picker (Android 13+, or back-ported via Google Play services). androidx falls
|
|
353
|
+
// back to ACTION_OPEN_DOCUMENT on devices where neither is available.
|
|
354
|
+
final ActivityResultContracts.PickVisualMedia.VisualMediaType mediaType;
|
|
355
355
|
if (pickVideo)
|
|
356
356
|
{
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
357
|
+
mediaType = ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE;
|
|
358
|
+
}
|
|
359
|
+
else if (pickBoth)
|
|
360
|
+
{
|
|
361
|
+
mediaType = ActivityResultContracts.PickVisualMedia.ImageAndVideo.INSTANCE;
|
|
362
362
|
}
|
|
363
363
|
else
|
|
364
364
|
{
|
|
365
|
-
|
|
366
|
-
libraryIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
367
|
-
libraryIntent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
|
|
368
|
-
libraryIntent.putExtra(Intent.CATEGORY_OPENABLE, true);
|
|
369
|
-
|
|
370
|
-
if (pickBoth)
|
|
371
|
-
{
|
|
372
|
-
libraryIntent.setType("image/* video/*");
|
|
373
|
-
} else {
|
|
374
|
-
libraryIntent.setType("image/*");
|
|
375
|
-
}
|
|
365
|
+
mediaType = ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE;
|
|
376
366
|
}
|
|
367
|
+
final PickVisualMediaRequest request = new PickVisualMediaRequest.Builder()
|
|
368
|
+
.setMediaType(mediaType)
|
|
369
|
+
.build();
|
|
370
|
+
final Intent libraryIntent = new ActivityResultContracts.PickVisualMedia()
|
|
371
|
+
.createIntent(currentActivity, request);
|
|
372
|
+
final int requestCode = pickVideo ? REQUEST_LAUNCH_VIDEO_LIBRARY : REQUEST_LAUNCH_IMAGE_LIBRARY;
|
|
377
373
|
|
|
378
374
|
try
|
|
379
375
|
{
|
|
380
|
-
|
|
381
|
-
if (ReadableMapUtils.hasAndNotEmptyString(options, "chooseWhichLibraryTitle"))
|
|
382
|
-
{
|
|
383
|
-
chooseWhichLibraryTitle = options.getString("chooseWhichLibraryTitle");
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
currentActivity.startActivityForResult(Intent.createChooser(libraryIntent, chooseWhichLibraryTitle), requestCode);
|
|
376
|
+
currentActivity.startActivityForResult(libraryIntent, requestCode);
|
|
387
377
|
}
|
|
388
378
|
catch (ActivityNotFoundException e)
|
|
389
379
|
{
|
|
@@ -394,6 +384,20 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
394
384
|
});
|
|
395
385
|
}
|
|
396
386
|
|
|
387
|
+
/**
|
|
388
|
+
* Ask for ACCESS_MEDIA_LOCATION once per library launch when it isn't granted yet. If the user has
|
|
389
|
+
* already denied it permanently the request returns immediately without UI.
|
|
390
|
+
*/
|
|
391
|
+
private boolean shouldRequestMediaLocation(@NonNull final Activity activity)
|
|
392
|
+
{
|
|
393
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || mediaLocationRequested)
|
|
394
|
+
{
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
return ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_MEDIA_LOCATION)
|
|
398
|
+
!= PackageManager.PERMISSION_GRANTED;
|
|
399
|
+
}
|
|
400
|
+
|
|
397
401
|
@Override
|
|
398
402
|
public void onActivityResult(Activity activity, final int requestCode, final int resultCode, final Intent data) {
|
|
399
403
|
final ImagePickerModule self = this;
|
|
@@ -417,6 +421,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
417
421
|
}
|
|
418
422
|
|
|
419
423
|
Uri uri = null;
|
|
424
|
+
boolean fromLibrary = false;
|
|
420
425
|
switch (requestCode)
|
|
421
426
|
{
|
|
422
427
|
case REQUEST_LAUNCH_IMAGE_CAPTURE:
|
|
@@ -424,35 +429,44 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
424
429
|
break;
|
|
425
430
|
|
|
426
431
|
case REQUEST_LAUNCH_IMAGE_LIBRARY:
|
|
427
|
-
uri = data.getData();
|
|
428
|
-
if(uri == null){
|
|
429
|
-
responseHelper.
|
|
432
|
+
uri = data != null ? data.getData() : null;
|
|
433
|
+
if (uri == null) {
|
|
434
|
+
responseHelper.invokeError(callback, "Could not read photo");
|
|
435
|
+
callback = null;
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
// mediaType "mixed" can hand back a video from the image request code.
|
|
439
|
+
final String mimeType = self.getContext().getContentResolver().getType(uri);
|
|
440
|
+
if (mimeType != null && mimeType.startsWith("video/")) {
|
|
441
|
+
responseHelper.putString("uri", uri.toString());
|
|
430
442
|
responseHelper.invokeResponse(callback);
|
|
431
443
|
callback = null;
|
|
432
444
|
return;
|
|
433
445
|
}
|
|
446
|
+
fromLibrary = true;
|
|
434
447
|
break;
|
|
435
448
|
|
|
436
449
|
case REQUEST_LAUNCH_VIDEO_LIBRARY:
|
|
437
|
-
responseHelper.putString("uri", data.getData().toString());
|
|
438
|
-
responseHelper.invokeResponse(callback);
|
|
439
|
-
callback = null;
|
|
440
|
-
return;
|
|
441
|
-
|
|
442
450
|
case REQUEST_LAUNCH_VIDEO_CAPTURE:
|
|
451
|
+
if (data == null || data.getData() == null) {
|
|
452
|
+
responseHelper.invokeError(callback, "Could not read video");
|
|
453
|
+
callback = null;
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
443
456
|
responseHelper.putString("uri", data.getData().toString());
|
|
444
457
|
responseHelper.invokeResponse(callback);
|
|
445
458
|
callback = null;
|
|
446
459
|
return;
|
|
447
460
|
}
|
|
448
461
|
|
|
462
|
+
// EXIF (location/timestamp/orientation). For library picks, ask for the unredacted original
|
|
463
|
+
// so GPS survives when ACCESS_MEDIA_LOCATION is granted and the provider honours it.
|
|
449
464
|
ReadExifResult result = null;
|
|
450
|
-
try {
|
|
451
|
-
|
|
452
|
-
if(imageInputStream != null){
|
|
465
|
+
try (InputStream imageInputStream = openExifInputStream(uri, fromLibrary)) {
|
|
466
|
+
if (imageInputStream != null) {
|
|
453
467
|
result = readExifInterface(imageInputStream, responseHelper);
|
|
454
468
|
}
|
|
455
|
-
} catch(IOException ex){
|
|
469
|
+
} catch (IOException ex) {
|
|
456
470
|
responseHelper.invokeError(callback, ex.getMessage());
|
|
457
471
|
callback = null;
|
|
458
472
|
return;
|
|
@@ -471,31 +485,26 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
471
485
|
|
|
472
486
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
|
473
487
|
options.inJustDecodeBounds = true;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
imageBitmap = BitmapFactory.decodeStream(imageInputStream, null, options);
|
|
478
|
-
} catch(FileNotFoundException ex){
|
|
488
|
+
try (InputStream imageInputStream = self.getContext().getContentResolver().openInputStream(uri)) {
|
|
489
|
+
BitmapFactory.decodeStream(imageInputStream, null, options);
|
|
490
|
+
} catch (IOException ex) {
|
|
479
491
|
responseHelper.invokeError(callback, "Could not find file");
|
|
480
492
|
callback = null;
|
|
481
493
|
return;
|
|
482
494
|
}
|
|
483
495
|
|
|
484
|
-
|
|
485
496
|
int initialWidth = options.outWidth;
|
|
486
497
|
int initialHeight = options.outHeight;
|
|
487
498
|
|
|
488
|
-
|
|
489
499
|
// don't create a new file if contraint are respected
|
|
490
500
|
if (imageConfig.useOriginal(initialWidth, initialHeight, result.currentRotation))
|
|
491
501
|
{
|
|
492
502
|
responseHelper.putInt("width", initialWidth);
|
|
493
503
|
responseHelper.putInt("height", initialHeight);
|
|
494
504
|
|
|
495
|
-
try {
|
|
496
|
-
InputStream imageInputStream = self.getContext().getContentResolver().openInputStream(uri);
|
|
505
|
+
try (InputStream imageInputStream = self.getContext().getContentResolver().openInputStream(uri)) {
|
|
497
506
|
updatedResultResponse(imageInputStream);
|
|
498
|
-
} catch(
|
|
507
|
+
} catch (IOException ex) {
|
|
499
508
|
responseHelper.invokeError(callback, "Could not find file");
|
|
500
509
|
callback = null;
|
|
501
510
|
return;
|
|
@@ -505,10 +514,9 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
505
514
|
{
|
|
506
515
|
Bitmap resizedImage = null;
|
|
507
516
|
ImageConfig rotatedImageConfig = imageConfig.withRotation(result.currentRotation);
|
|
508
|
-
try {
|
|
509
|
-
InputStream imageInputStream = self.getContext().getContentResolver().openInputStream(uri);
|
|
517
|
+
try (InputStream imageInputStream = self.getContext().getContentResolver().openInputStream(uri)) {
|
|
510
518
|
resizedImage = getResizedImage(imageInputStream, rotatedImageConfig, initialWidth, initialHeight);
|
|
511
|
-
} catch(
|
|
519
|
+
} catch (IOException ex) {
|
|
512
520
|
responseHelper.invokeError(callback, "Could not find file");
|
|
513
521
|
callback = null;
|
|
514
522
|
return;
|
|
@@ -528,14 +536,8 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
528
536
|
ByteArrayInputStream bs = new ByteArrayInputStream(bitmapdata);
|
|
529
537
|
updatedResultResponse(bs);
|
|
530
538
|
resizedImage.recycle();
|
|
531
|
-
resizedImage = null;
|
|
532
539
|
}
|
|
533
540
|
}
|
|
534
|
-
if(imageBitmap != null){
|
|
535
|
-
imageBitmap.recycle();
|
|
536
|
-
imageBitmap = null;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
541
|
|
|
540
542
|
responseHelper.invokeResponse(callback);
|
|
541
543
|
self.callback = null;
|
|
@@ -544,6 +546,28 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
544
546
|
});
|
|
545
547
|
}
|
|
546
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Open a stream for EXIF parsing. On Android 10+ location tags are redacted unless we hold
|
|
551
|
+
* ACCESS_MEDIA_LOCATION and ask for the original via MediaStore.setRequireOriginal. Providers that
|
|
552
|
+
* don't support that (e.g. some Photo Picker configurations) throw, in which case we fall back to the
|
|
553
|
+
* plain (redacted) stream.
|
|
554
|
+
*/
|
|
555
|
+
private @Nullable InputStream openExifInputStream(@NonNull final Uri uri, final boolean fromLibrary) throws FileNotFoundException
|
|
556
|
+
{
|
|
557
|
+
if (fromLibrary && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
|
558
|
+
{
|
|
559
|
+
try
|
|
560
|
+
{
|
|
561
|
+
return getContext().getContentResolver().openInputStream(MediaStore.setRequireOriginal(uri));
|
|
562
|
+
}
|
|
563
|
+
catch (UnsupportedOperationException | SecurityException | IllegalArgumentException | FileNotFoundException e)
|
|
564
|
+
{
|
|
565
|
+
// fall through to the plain uri
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return getContext().getContentResolver().openInputStream(uri);
|
|
569
|
+
}
|
|
570
|
+
|
|
547
571
|
public void invokeCustomButton(@NonNull final String action)
|
|
548
572
|
{
|
|
549
573
|
responseHelper.invokeCustomButton(this.callback, action);
|
|
@@ -564,7 +588,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
564
588
|
|
|
565
589
|
public @NonNull Activity getActivity()
|
|
566
590
|
{
|
|
567
|
-
return getCurrentActivity();
|
|
591
|
+
return getReactApplicationContext().getCurrentActivity();
|
|
568
592
|
}
|
|
569
593
|
|
|
570
594
|
|
|
@@ -584,130 +608,89 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
584
608
|
@NonNull final Callback callback,
|
|
585
609
|
@NonNull final int requestCode)
|
|
586
610
|
{
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
final int cameraPermission = ActivityCompat
|
|
598
|
-
.checkSelfPermission(activity, Manifest.permission.CAMERA);
|
|
599
|
-
|
|
600
|
-
boolean permissionsGranted = false;
|
|
601
|
-
|
|
602
|
-
switch (requestCode) {
|
|
603
|
-
case REQUEST_PERMISSIONS_FOR_LIBRARY:
|
|
604
|
-
permissionsGranted = libraryPermission == PackageManager.PERMISSION_GRANTED;
|
|
605
|
-
break;
|
|
606
|
-
case REQUEST_PERMISSIONS_FOR_CAMERA:
|
|
607
|
-
permissionsGranted = cameraPermission == PackageManager.PERMISSION_GRANTED && writePermission == PackageManager.PERMISSION_GRANTED;
|
|
608
|
-
break;
|
|
611
|
+
// Only the camera needs a runtime permission. Captures are written to app-specific storage
|
|
612
|
+
// (getExternalFilesDir) and the library uses the system Photo Picker, so no storage permission
|
|
613
|
+
// is required on any supported API level.
|
|
614
|
+
final boolean permissionsGranted = ActivityCompat
|
|
615
|
+
.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
|
|
616
|
+
if (permissionsGranted)
|
|
617
|
+
{
|
|
618
|
+
return true;
|
|
609
619
|
}
|
|
610
620
|
|
|
611
|
-
if (
|
|
621
|
+
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.CAMERA))
|
|
612
622
|
{
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (dontAskAgain)
|
|
621
|
-
{
|
|
622
|
-
final AlertDialog dialog = PermissionUtils
|
|
623
|
-
.explainingDialog(this, options, new PermissionUtils.OnExplainingPermissionCallback()
|
|
623
|
+
final AlertDialog dialog = PermissionUtils
|
|
624
|
+
.explainingDialog(this, options, new PermissionUtils.OnExplainingPermissionCallback()
|
|
625
|
+
{
|
|
626
|
+
@Override
|
|
627
|
+
public void onCancel(WeakReference<ImagePickerModule> moduleInstance,
|
|
628
|
+
DialogInterface dialogInterface)
|
|
624
629
|
{
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
DialogInterface dialogInterface)
|
|
630
|
+
final ImagePickerModule module = moduleInstance.get();
|
|
631
|
+
if (module == null)
|
|
628
632
|
{
|
|
629
|
-
|
|
630
|
-
if (module == null)
|
|
631
|
-
{
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
module.doOnCancel();
|
|
633
|
+
return;
|
|
635
634
|
}
|
|
635
|
+
module.doOnCancel();
|
|
636
|
+
}
|
|
636
637
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
638
|
+
@Override
|
|
639
|
+
public void onReTry(WeakReference<ImagePickerModule> moduleInstance,
|
|
640
|
+
DialogInterface dialogInterface)
|
|
641
|
+
{
|
|
642
|
+
final ImagePickerModule module = moduleInstance.get();
|
|
643
|
+
if (module == null)
|
|
640
644
|
{
|
|
641
|
-
|
|
642
|
-
if (module == null)
|
|
643
|
-
{
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
647
|
-
Uri uri = Uri.fromParts("package", module.getContext().getPackageName(), null);
|
|
648
|
-
intent.setData(uri);
|
|
649
|
-
final Activity innerActivity = module.getActivity();
|
|
650
|
-
if (innerActivity == null)
|
|
651
|
-
{
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
innerActivity.startActivityForResult(intent, 1);
|
|
645
|
+
return;
|
|
655
646
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
647
|
+
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
648
|
+
Uri uri = Uri.fromParts("package", module.getContext().getPackageName(), null);
|
|
649
|
+
intent.setData(uri);
|
|
650
|
+
final Activity innerActivity = module.getActivity();
|
|
651
|
+
if (innerActivity == null)
|
|
652
|
+
{
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
innerActivity.startActivityForResult(intent, 1);
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
if (dialog != null) {
|
|
659
|
+
dialog.show();
|
|
661
660
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
String[] PERMISSIONS;
|
|
665
|
-
switch (requestCode) {
|
|
666
|
-
case REQUEST_PERMISSIONS_FOR_LIBRARY:
|
|
667
|
-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
|
668
|
-
PERMISSIONS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
|
669
|
-
} else {
|
|
670
|
-
PERMISSIONS = new String[]{Manifest.permission.READ_MEDIA_IMAGES};
|
|
671
|
-
}
|
|
672
|
-
break;
|
|
673
|
-
case REQUEST_PERMISSIONS_FOR_CAMERA:
|
|
674
|
-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
|
675
|
-
PERMISSIONS = new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
|
676
|
-
} else {
|
|
677
|
-
PERMISSIONS = new String[]{Manifest.permission.CAMERA};
|
|
678
|
-
}
|
|
679
|
-
break;
|
|
680
|
-
default:
|
|
681
|
-
PERMISSIONS = new String[]{};
|
|
682
|
-
break;
|
|
683
|
-
}
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
684
663
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
664
|
+
requestPermissions(activity, new String[]{Manifest.permission.CAMERA}, requestCode);
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
private void requestPermissions(@NonNull final Activity activity,
|
|
669
|
+
@NonNull final String[] permissions,
|
|
670
|
+
final int requestCode)
|
|
671
|
+
{
|
|
672
|
+
if (activity instanceof ReactActivity)
|
|
673
|
+
{
|
|
674
|
+
((ReactActivity) activity).requestPermissions(permissions, requestCode, listener);
|
|
675
|
+
}
|
|
676
|
+
else if (activity instanceof PermissionAwareActivity) {
|
|
677
|
+
((PermissionAwareActivity) activity).requestPermissions(permissions, requestCode, listener);
|
|
678
|
+
}
|
|
679
|
+
else if (activity instanceof OnImagePickerPermissionsCallback)
|
|
680
|
+
{
|
|
681
|
+
((OnImagePickerPermissionsCallback) activity).setPermissionListener(listener);
|
|
682
|
+
ActivityCompat.requestPermissions(activity, permissions, requestCode);
|
|
683
|
+
}
|
|
684
|
+
else
|
|
685
|
+
{
|
|
686
|
+
final String errorDescription = new StringBuilder(activity.getClass().getSimpleName())
|
|
687
|
+
.append(" must implement ")
|
|
688
|
+
.append(OnImagePickerPermissionsCallback.class.getSimpleName())
|
|
689
|
+
.append(" or ")
|
|
690
|
+
.append(PermissionAwareActivity.class.getSimpleName())
|
|
691
|
+
.toString();
|
|
692
|
+
throw new UnsupportedOperationException(errorDescription);
|
|
709
693
|
}
|
|
710
|
-
return true;
|
|
711
694
|
}
|
|
712
695
|
|
|
713
696
|
private boolean isCameraAvailable() {
|
package/ios/ImagePickerManager.h
CHANGED
|
@@ -6,6 +6,6 @@ typedef NS_ENUM(NSInteger, RNImagePickerTarget) {
|
|
|
6
6
|
RNImagePickerTargetLibrarySingleImage,
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
@interface ImagePickerManager : NSObject <RCTBridgeModule, UINavigationControllerDelegate,
|
|
9
|
+
@interface ImagePickerManager : NSObject <RCTBridgeModule, UINavigationControllerDelegate, UIImagePickerControllerDelegate>
|
|
10
10
|
|
|
11
11
|
@end
|