diff --git a/README.md b/README.md
index 0639ce4..281b122 100644
--- a/README.md
+++ b/README.md
@@ -184,6 +184,18 @@ quality, even if a `quality` parameter is specified. To avoid common
memory problems, set `Camera.destinationType` to `FILE_URI` rather
than `DATA_URL`.
+__NOTE__: To use `saveToPhotoAlbum` option on Android 9 (API 28) and lower, the `WRITE_EXTERNAL_STORAGE` permission must be declared.
+
+To do this, add the following in your `config.xml`:
+
+```xml
+
+
+
+```
+
+Android 10 (API 29) and later devices does not require `WRITE_EXTERNAL_STORAGE` permission. If your application only supports Android 10 or later, then this step is not necessary.
+
#### FILE_URI Usage
When `FILE_URI` is used, the returned path is not directly usable. The file path needs to be resolved into
@@ -301,7 +313,7 @@ Optional parameters to customize the camera settings.
| targetHeight | number
| | Height in pixels to scale image. Must be used with `targetWidth`. Aspect ratio remains constant. |
| mediaType | [MediaType](#module_Camera.MediaType)
| PICTURE
| Set the type of media to select from. Only works when `PictureSourceType` is `PHOTOLIBRARY` or `SAVEDPHOTOALBUM`. |
| correctOrientation | Boolean
| | Rotate the image to correct for the orientation of the device during capture. |
-| saveToPhotoAlbum | Boolean
| | Save the image to the photo album on the device after capture. |
+| saveToPhotoAlbum | Boolean
| | Save the image to the photo album on the device after capture.
See [Android Quirks](#cameragetpicturesuccesscallback-errorcallback-options). |
| popoverOptions | [CameraPopoverOptions](#module_CameraPopoverOptions)
| | iOS-only options that specify popover location in iPad. |
| cameraDirection | [Direction](#module_Camera.Direction)
| BACK
| Choose the camera to use (front- or back-facing). |
diff --git a/src/android/CameraLauncher.java b/src/android/CameraLauncher.java
index 8817301..aea896e 100644
--- a/src/android/CameraLauncher.java
+++ b/src/android/CameraLauncher.java
@@ -193,15 +193,16 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.callTakePicture(destType, encodingType);
}
else if ((this.srcType == PHOTOLIBRARY) || (this.srcType == SAVEDPHOTOALBUM)) {
- // FIXME: Stop always requesting the permission
- String[] permissions = getPermissions(true, mediaType);
- if(!hasPermissions(permissions)) {
- PermissionHelper.requestPermissions(this, SAVE_TO_ALBUM_SEC, permissions);
- } else {
- this.getImage(this.srcType, destType);
- }
+ this.getImage(this.srcType, destType);
}
}
+ catch (IllegalStateException e)
+ {
+ callbackContext.error(e.getLocalizedMessage());
+ PluginResult r = new PluginResult(PluginResult.Status.ERROR);
+ callbackContext.sendPluginResult(r);
+ return true;
+ }
catch (IllegalArgumentException e)
{
callbackContext.error("Illegal Argument Exception");
@@ -223,22 +224,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
// LOCAL METHODS
//--------------------------------------------------------------------------
- private String[] getPermissions(boolean storageOnly, int mediaType) {
- ArrayList permissions = new ArrayList<>();
-
- if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
- // Android API 30 or lower
- permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
- permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
- }
- if (!storageOnly) {
- // Add camera permission when not storage.
- permissions.add(Manifest.permission.CAMERA);
- }
-
- return permissions.toArray(new String[0]);
- }
-
private String getTempDirectoryPath() {
File cache = cordova.getActivity().getCacheDir();
// Create the cache directory if it doesn't exist
@@ -260,47 +245,64 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
* @param returnType Set the type of image to return.
* @param encodingType Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
*/
- public void callTakePicture(int returnType, int encodingType) {
- String[] storagePermissions = getPermissions(true, mediaType);
- boolean saveAlbumPermission;
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- saveAlbumPermission = this.saveToPhotoAlbum ? hasPermissions(storagePermissions) : true;
- } else {
- saveAlbumPermission = hasPermissions(storagePermissions);
- }
- boolean takePicturePermission = PermissionHelper.hasPermission(this, Manifest.permission.CAMERA);
+ public void callTakePicture(int returnType, int encodingType) throws IllegalStateException {
// CB-10120: The CAMERA permission does not need to be requested unless it is declared
// in AndroidManifest.xml. This plugin does not declare it, but others may and so we must
// check the package info to determine if the permission is present.
+ boolean manifestContainsCameraPermission = false;
- if (!takePicturePermission) {
- takePicturePermission = true;
- try {
- PackageManager packageManager = this.cordova.getActivity().getPackageManager();
- String[] permissionsInPackage = packageManager.getPackageInfo(this.cordova.getActivity().getPackageName(), PackageManager.GET_PERMISSIONS).requestedPermissions;
- if (permissionsInPackage != null) {
- for (String permission : permissionsInPackage) {
- if (permission.equals(Manifest.permission.CAMERA)) {
- takePicturePermission = false;
- break;
- }
- }
- }
- } catch (NameNotFoundException e) {
- // We are requesting the info for our package, so this should
- // never be caught
- }
+ // write permission is not necessary, unless if we are saving to photo album
+ // On API 29+ devices, write permission is completely obsolete and not required.
+ boolean manifestContainsWriteExternalPermission = false;
+
+ boolean cameraPermissionGranted = PermissionHelper.hasPermission(this, Manifest.permission.CAMERA);
+ boolean writeExternalPermissionGranted = false;
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
+ writeExternalPermissionGranted = PermissionHelper.hasPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ }
+ else {
+ writeExternalPermissionGranted = true;
}
- if (takePicturePermission && saveAlbumPermission) {
+ try {
+ PackageManager packageManager = this.cordova.getActivity().getPackageManager();
+ String[] permissionsInPackage = packageManager.getPackageInfo(this.cordova.getActivity().getPackageName(), PackageManager.GET_PERMISSIONS).requestedPermissions;
+ if (permissionsInPackage != null) {
+ for (String permission : permissionsInPackage) {
+ if (permission.equals(Manifest.permission.CAMERA)) {
+ manifestContainsCameraPermission = true;
+ }
+ else if (permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ manifestContainsWriteExternalPermission = true;
+ }
+ }
+ }
+ } catch (NameNotFoundException e) {
+ // We are requesting the info for our package, so this should
+ // never be caught
+ }
+
+ ArrayList requiredPermissions = new ArrayList<>();
+ if (manifestContainsCameraPermission && !cameraPermissionGranted) {
+ requiredPermissions.add(Manifest.permission.CAMERA);
+ }
+
+ if (saveToPhotoAlbum && !writeExternalPermissionGranted) {
+ // This block only applies for API 24-28
+ // because writeExternalPermissionGranted is always true on API 29+
+ if (!manifestContainsWriteExternalPermission) {
+ throw new IllegalStateException("WRITE_EXTERNAL_STORAGE permission not declared in AndroidManifest");
+ }
+
+ requiredPermissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ }
+
+ if (!requiredPermissions.isEmpty()) {
+ PermissionHelper.requestPermissions(this, TAKE_PIC_SEC, requiredPermissions.toArray(new String[0]));
+ }
+ else {
takePicture(returnType, encodingType);
- } else if (saveAlbumPermission) {
- PermissionHelper.requestPermission(this, TAKE_PIC_SEC, Manifest.permission.CAMERA);
- } else if (takePicturePermission) {
- PermissionHelper.requestPermissions(this, TAKE_PIC_SEC, storagePermissions);
- } else {
- PermissionHelper.requestPermissions(this, TAKE_PIC_SEC, getPermissions(false, mediaType));
}
}
@@ -1358,15 +1360,6 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect
this.callbackContext = callbackContext;
}
- private boolean hasPermissions(String[] permissions) {
- for (String permission: permissions) {
- if (!PermissionHelper.hasPermission(this, permission)) {
- return false;
- }
- }
- return true;
- }
-
/**
* Gets the ideal buffer size for processing streams of data.
*