@connectedbits/react-native-image-picker 2.4.16-rc2 → 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 +227 -243
- 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
|
@@ -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
|
|
|
@@ -152,23 +158,22 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
152
158
|
|
|
153
159
|
@ReactMethod
|
|
154
160
|
public void showImagePicker(final ReadableMap options, final Callback callback) {
|
|
161
|
+
final ImagePickerModule self = this;
|
|
162
|
+
final Activity currentActivity = getReactApplicationContext().getCurrentActivity();
|
|
155
163
|
Handler handler = new Handler(this.reactContext.getMainLooper());
|
|
164
|
+
this.callback = callback;
|
|
165
|
+
this.options = options;
|
|
166
|
+
if (currentActivity == null)
|
|
167
|
+
{
|
|
168
|
+
responseHelper.invokeError(callback, "can't find current Activity");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
imageConfig = new ImageConfig(0, 0, 100, 0, false);
|
|
156
172
|
handler.post(new Runnable() {
|
|
157
173
|
@Override
|
|
158
174
|
public void run() {
|
|
159
|
-
Activity currentActivity = getCurrentActivity();
|
|
160
|
-
|
|
161
|
-
if (currentActivity == null)
|
|
162
|
-
{
|
|
163
|
-
responseHelper.invokeError(callback, "can't find current Activity");
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
this.callback = callback;
|
|
168
|
-
this.options = options;
|
|
169
|
-
imageConfig = new ImageConfig(0, 0, 100, 0, false);
|
|
170
175
|
|
|
171
|
-
final AlertDialog dialog = UI.chooseDialog(
|
|
176
|
+
final AlertDialog dialog = UI.chooseDialog(self, options, new UI.OnAction()
|
|
172
177
|
{
|
|
173
178
|
@Override
|
|
174
179
|
public void onTakePhoto(@NonNull final ImagePickerModule module)
|
|
@@ -233,34 +238,35 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
233
238
|
@ReactMethod
|
|
234
239
|
public void launchCamera(final ReadableMap options, final Callback callback)
|
|
235
240
|
{
|
|
236
|
-
|
|
237
|
-
handler
|
|
238
|
-
|
|
239
|
-
public void run() {
|
|
240
|
-
permissionRequestCallback = callback;
|
|
241
|
+
final ImagePickerModule self = this;
|
|
242
|
+
Handler handler = new Handler(this.getContext().getMainLooper());
|
|
243
|
+
permissionRequestCallback = callback;
|
|
241
244
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
if (!isCameraAvailable())
|
|
246
|
+
{
|
|
247
|
+
responseHelper.invokeError(callback, "Camera not available");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
247
250
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
251
|
+
final Activity currentActivity = getReactApplicationContext().getCurrentActivity();
|
|
252
|
+
if (currentActivity == null)
|
|
253
|
+
{
|
|
254
|
+
responseHelper.invokeError(callback, "can't find current Activity");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
254
257
|
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
this.callback = callback;
|
|
259
|
+
this.options = options;
|
|
257
260
|
|
|
261
|
+
handler.post(new Runnable() {
|
|
262
|
+
@Override
|
|
263
|
+
public void run() {
|
|
258
264
|
if (!permissionsCheck(currentActivity, callback, REQUEST_PERMISSIONS_FOR_CAMERA))
|
|
259
265
|
{
|
|
260
266
|
return;
|
|
261
267
|
}
|
|
262
268
|
|
|
263
|
-
parseOptions(
|
|
269
|
+
parseOptions(options);
|
|
264
270
|
|
|
265
271
|
int requestCode;
|
|
266
272
|
Intent cameraIntent;
|
|
@@ -280,10 +286,10 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
280
286
|
requestCode = REQUEST_LAUNCH_IMAGE_CAPTURE;
|
|
281
287
|
cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
|
282
288
|
|
|
283
|
-
final File original = createNewFile(
|
|
289
|
+
final File original = createNewFile(self.getContext(), options, false);
|
|
284
290
|
|
|
285
291
|
if (original != null) {
|
|
286
|
-
cameraCaptureURI = RealPathUtil.compatUriFromFile(
|
|
292
|
+
cameraCaptureURI = RealPathUtil.compatUriFromFile(self.getContext(), original);
|
|
287
293
|
}else {
|
|
288
294
|
responseHelper.invokeError(callback, "Couldn't get file path for photo");
|
|
289
295
|
return;
|
|
@@ -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 = reactContext.getPackageManager().queryIntentActivities(cameraIntent, PackageManager.MATCH_DEFAULT_ONLY);
|
|
304
|
-
for (ResolveInfo resolveInfo : resInfoList) {
|
|
305
|
-
String packageName = resolveInfo.activityInfo.packageName;
|
|
306
|
-
reactContext.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
|
{
|
|
@@ -328,62 +325,55 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
328
325
|
@ReactMethod
|
|
329
326
|
public void launchImageLibrary(final ReadableMap options, final Callback callback)
|
|
330
327
|
{
|
|
331
|
-
|
|
332
|
-
handler.post(new Runnable() {
|
|
333
|
-
@Override
|
|
334
|
-
public void run(){
|
|
335
|
-
permissionRequestCallback = callback;
|
|
328
|
+
permissionRequestCallback = callback;
|
|
336
329
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
330
|
+
final Activity currentActivity = getReactApplicationContext().getCurrentActivity();
|
|
331
|
+
if (currentActivity == null) {
|
|
332
|
+
responseHelper.invokeError(callback, "can't find current Activity");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
342
335
|
|
|
343
|
-
|
|
344
|
-
|
|
336
|
+
this.callback = callback;
|
|
337
|
+
this.options = options;
|
|
345
338
|
|
|
346
|
-
|
|
339
|
+
Handler handler = new Handler(this.getContext().getMainLooper());
|
|
340
|
+
handler.post(new Runnable() {
|
|
341
|
+
@Override
|
|
342
|
+
public void run(){
|
|
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
|
-
parseOptions(
|
|
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,13 +384,28 @@ 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
|
-
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
|
|
399
|
-
|
|
402
|
+
public void onActivityResult(Activity activity, final int requestCode, final int resultCode, final Intent data) {
|
|
403
|
+
final ImagePickerModule self = this;
|
|
404
|
+
Handler handler = new Handler(this.getContext().getMainLooper());
|
|
400
405
|
handler.post(new Runnable() {
|
|
401
406
|
@Override
|
|
402
407
|
public void run() {
|
|
403
|
-
if (passResult(requestCode))
|
|
408
|
+
if (self.passResult(requestCode))
|
|
404
409
|
{
|
|
405
410
|
return;
|
|
406
411
|
}
|
|
@@ -416,6 +421,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
416
421
|
}
|
|
417
422
|
|
|
418
423
|
Uri uri = null;
|
|
424
|
+
boolean fromLibrary = false;
|
|
419
425
|
switch (requestCode)
|
|
420
426
|
{
|
|
421
427
|
case REQUEST_LAUNCH_IMAGE_CAPTURE:
|
|
@@ -423,35 +429,44 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
423
429
|
break;
|
|
424
430
|
|
|
425
431
|
case REQUEST_LAUNCH_IMAGE_LIBRARY:
|
|
426
|
-
uri = data.getData();
|
|
427
|
-
if(uri == null){
|
|
428
|
-
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());
|
|
429
442
|
responseHelper.invokeResponse(callback);
|
|
430
443
|
callback = null;
|
|
431
444
|
return;
|
|
432
445
|
}
|
|
446
|
+
fromLibrary = true;
|
|
433
447
|
break;
|
|
434
448
|
|
|
435
449
|
case REQUEST_LAUNCH_VIDEO_LIBRARY:
|
|
436
|
-
responseHelper.putString("uri", data.getData().toString());
|
|
437
|
-
responseHelper.invokeResponse(callback);
|
|
438
|
-
callback = null;
|
|
439
|
-
return;
|
|
440
|
-
|
|
441
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
|
+
}
|
|
442
456
|
responseHelper.putString("uri", data.getData().toString());
|
|
443
457
|
responseHelper.invokeResponse(callback);
|
|
444
458
|
callback = null;
|
|
445
459
|
return;
|
|
446
460
|
}
|
|
447
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.
|
|
448
464
|
ReadExifResult result = null;
|
|
449
|
-
try {
|
|
450
|
-
|
|
451
|
-
if(imageInputStream != null){
|
|
465
|
+
try (InputStream imageInputStream = openExifInputStream(uri, fromLibrary)) {
|
|
466
|
+
if (imageInputStream != null) {
|
|
452
467
|
result = readExifInterface(imageInputStream, responseHelper);
|
|
453
468
|
}
|
|
454
|
-
} catch(IOException ex){
|
|
469
|
+
} catch (IOException ex) {
|
|
455
470
|
responseHelper.invokeError(callback, ex.getMessage());
|
|
456
471
|
callback = null;
|
|
457
472
|
return;
|
|
@@ -470,31 +485,26 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
470
485
|
|
|
471
486
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
|
472
487
|
options.inJustDecodeBounds = true;
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
imageBitmap = BitmapFactory.decodeStream(imageInputStream, null, options);
|
|
477
|
-
} catch(FileNotFoundException ex){
|
|
488
|
+
try (InputStream imageInputStream = self.getContext().getContentResolver().openInputStream(uri)) {
|
|
489
|
+
BitmapFactory.decodeStream(imageInputStream, null, options);
|
|
490
|
+
} catch (IOException ex) {
|
|
478
491
|
responseHelper.invokeError(callback, "Could not find file");
|
|
479
492
|
callback = null;
|
|
480
493
|
return;
|
|
481
494
|
}
|
|
482
495
|
|
|
483
|
-
|
|
484
496
|
int initialWidth = options.outWidth;
|
|
485
497
|
int initialHeight = options.outHeight;
|
|
486
498
|
|
|
487
|
-
|
|
488
499
|
// don't create a new file if contraint are respected
|
|
489
500
|
if (imageConfig.useOriginal(initialWidth, initialHeight, result.currentRotation))
|
|
490
501
|
{
|
|
491
502
|
responseHelper.putInt("width", initialWidth);
|
|
492
503
|
responseHelper.putInt("height", initialHeight);
|
|
493
504
|
|
|
494
|
-
try {
|
|
495
|
-
InputStream imageInputStream = reactContext.getContentResolver().openInputStream(uri);
|
|
505
|
+
try (InputStream imageInputStream = self.getContext().getContentResolver().openInputStream(uri)) {
|
|
496
506
|
updatedResultResponse(imageInputStream);
|
|
497
|
-
} catch(
|
|
507
|
+
} catch (IOException ex) {
|
|
498
508
|
responseHelper.invokeError(callback, "Could not find file");
|
|
499
509
|
callback = null;
|
|
500
510
|
return;
|
|
@@ -504,10 +514,9 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
504
514
|
{
|
|
505
515
|
Bitmap resizedImage = null;
|
|
506
516
|
ImageConfig rotatedImageConfig = imageConfig.withRotation(result.currentRotation);
|
|
507
|
-
try {
|
|
508
|
-
InputStream imageInputStream = reactContext.getContentResolver().openInputStream(uri);
|
|
517
|
+
try (InputStream imageInputStream = self.getContext().getContentResolver().openInputStream(uri)) {
|
|
509
518
|
resizedImage = getResizedImage(imageInputStream, rotatedImageConfig, initialWidth, initialHeight);
|
|
510
|
-
} catch(
|
|
519
|
+
} catch (IOException ex) {
|
|
511
520
|
responseHelper.invokeError(callback, "Could not find file");
|
|
512
521
|
callback = null;
|
|
513
522
|
return;
|
|
@@ -527,22 +536,38 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
527
536
|
ByteArrayInputStream bs = new ByteArrayInputStream(bitmapdata);
|
|
528
537
|
updatedResultResponse(bs);
|
|
529
538
|
resizedImage.recycle();
|
|
530
|
-
resizedImage = null;
|
|
531
539
|
}
|
|
532
540
|
}
|
|
533
|
-
if(imageBitmap != null){
|
|
534
|
-
imageBitmap.recycle();
|
|
535
|
-
imageBitmap = null;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
541
|
|
|
539
542
|
responseHelper.invokeResponse(callback);
|
|
540
|
-
callback = null;
|
|
541
|
-
|
|
543
|
+
self.callback = null;
|
|
544
|
+
self.options = null;
|
|
542
545
|
}
|
|
543
546
|
});
|
|
544
547
|
}
|
|
545
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
|
+
|
|
546
571
|
public void invokeCustomButton(@NonNull final String action)
|
|
547
572
|
{
|
|
548
573
|
responseHelper.invokeCustomButton(this.callback, action);
|
|
@@ -563,7 +588,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
563
588
|
|
|
564
589
|
public @NonNull Activity getActivity()
|
|
565
590
|
{
|
|
566
|
-
return getCurrentActivity();
|
|
591
|
+
return getReactApplicationContext().getCurrentActivity();
|
|
567
592
|
}
|
|
568
593
|
|
|
569
594
|
|
|
@@ -583,130 +608,89 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
|
|
|
583
608
|
@NonNull final Callback callback,
|
|
584
609
|
@NonNull final int requestCode)
|
|
585
610
|
{
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
final int cameraPermission = ActivityCompat
|
|
597
|
-
.checkSelfPermission(activity, Manifest.permission.CAMERA);
|
|
598
|
-
|
|
599
|
-
boolean permissionsGranted = false;
|
|
600
|
-
|
|
601
|
-
switch (requestCode) {
|
|
602
|
-
case REQUEST_PERMISSIONS_FOR_LIBRARY:
|
|
603
|
-
permissionsGranted = libraryPermission == PackageManager.PERMISSION_GRANTED;
|
|
604
|
-
break;
|
|
605
|
-
case REQUEST_PERMISSIONS_FOR_CAMERA:
|
|
606
|
-
permissionsGranted = cameraPermission == PackageManager.PERMISSION_GRANTED && writePermission == PackageManager.PERMISSION_GRANTED;
|
|
607
|
-
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;
|
|
608
619
|
}
|
|
609
620
|
|
|
610
|
-
if (
|
|
621
|
+
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.CAMERA))
|
|
611
622
|
{
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
if (dontAskAgain)
|
|
620
|
-
{
|
|
621
|
-
final AlertDialog dialog = PermissionUtils
|
|
622
|
-
.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)
|
|
623
629
|
{
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
DialogInterface dialogInterface)
|
|
630
|
+
final ImagePickerModule module = moduleInstance.get();
|
|
631
|
+
if (module == null)
|
|
627
632
|
{
|
|
628
|
-
|
|
629
|
-
if (module == null)
|
|
630
|
-
{
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
module.doOnCancel();
|
|
633
|
+
return;
|
|
634
634
|
}
|
|
635
|
+
module.doOnCancel();
|
|
636
|
+
}
|
|
635
637
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
638
|
+
@Override
|
|
639
|
+
public void onReTry(WeakReference<ImagePickerModule> moduleInstance,
|
|
640
|
+
DialogInterface dialogInterface)
|
|
641
|
+
{
|
|
642
|
+
final ImagePickerModule module = moduleInstance.get();
|
|
643
|
+
if (module == null)
|
|
639
644
|
{
|
|
640
|
-
|
|
641
|
-
if (module == null)
|
|
642
|
-
{
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
646
|
-
Uri uri = Uri.fromParts("package", module.getContext().getPackageName(), null);
|
|
647
|
-
intent.setData(uri);
|
|
648
|
-
final Activity innerActivity = module.getActivity();
|
|
649
|
-
if (innerActivity == null)
|
|
650
|
-
{
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
innerActivity.startActivityForResult(intent, 1);
|
|
645
|
+
return;
|
|
654
646
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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();
|
|
660
660
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
String[] PERMISSIONS;
|
|
664
|
-
switch (requestCode) {
|
|
665
|
-
case REQUEST_PERMISSIONS_FOR_LIBRARY:
|
|
666
|
-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
|
667
|
-
PERMISSIONS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
|
668
|
-
} else {
|
|
669
|
-
PERMISSIONS = new String[]{Manifest.permission.READ_MEDIA_IMAGES};
|
|
670
|
-
}
|
|
671
|
-
break;
|
|
672
|
-
case REQUEST_PERMISSIONS_FOR_CAMERA:
|
|
673
|
-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
|
674
|
-
PERMISSIONS = new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
|
675
|
-
} else {
|
|
676
|
-
PERMISSIONS = new String[]{Manifest.permission.CAMERA};
|
|
677
|
-
}
|
|
678
|
-
break;
|
|
679
|
-
default:
|
|
680
|
-
PERMISSIONS = new String[]{};
|
|
681
|
-
break;
|
|
682
|
-
}
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
683
663
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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);
|
|
708
693
|
}
|
|
709
|
-
return true;
|
|
710
694
|
}
|
|
711
695
|
|
|
712
696
|
private boolean isCameraAvailable() {
|