You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by no...@apache.org on 2024/10/28 18:21:43 UTC

(cordova-plugin-camera) branch master updated: refactor(android): Make WRITE_EXTERNAL_STORAGE optional (#909)

This is an automated email from the ASF dual-hosted git repository.

normanbreau pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cordova-plugin-camera.git


The following commit(s) were added to refs/heads/master by this push:
     new 48c4cdd  refactor(android): Make WRITE_EXTERNAL_STORAGE optional (#909)
48c4cdd is described below

commit 48c4cdd47fd0e3830ee1229bdeb58c5af7e2de51
Author: Norman Breau <no...@breautek.com>
AuthorDate: Mon Oct 28 15:21:37 2024 -0300

    refactor(android): Make WRITE_EXTERNAL_STORAGE optional (#909)
    
    * refactor(android): Rework permission management to make WRITE_EXTERNAL_STORAGE optional
    
    * removed unused getPermissions API
    
    * Proper error if WRITE_EXTERNAL_STORAGE is required but missing the declaration
    
    * removed obsolete hasPermissions API
---
 README.md                       |  14 ++++-
 src/android/CameraLauncher.java | 117 +++++++++++++++++++---------------------
 2 files changed, 68 insertions(+), 63 deletions(-)

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
+<config-file target="AndroidManifest.xml" parent="/*" xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
+</config-file>
+```
+
+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 | <code>number</code> |  | Height in pixels to scale image. Must be used with `targetWidth`. Aspect ratio remains constant. |
 | mediaType | <code>[MediaType](#module_Camera.MediaType)</code> | <code>PICTURE</code> | Set the type of media to select from.  Only works when `PictureSourceType` is `PHOTOLIBRARY` or `SAVEDPHOTOALBUM`. |
 | correctOrientation | <code>Boolean</code> |  | Rotate the image to correct for the orientation of the device during capture. |
-| saveToPhotoAlbum | <code>Boolean</code> |  | Save the image to the photo album on the device after capture. |
+| saveToPhotoAlbum | <code>Boolean</code> |  | Save the image to the photo album on the device after capture.<br />See [Android Quirks](#cameragetpicturesuccesscallback-errorcallback-options). |
 | popoverOptions | <code>[CameraPopoverOptions](#module_CameraPopoverOptions)</code> |  | iOS-only options that specify popover location in iPad. |
 | cameraDirection | <code>[Direction](#module_Camera.Direction)</code> | <code>BACK</code> | 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<String> 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;
-                        }
+        // 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;
+        }
+
+        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
             }
+        } catch (NameNotFoundException e) {
+            // We are requesting the info for our package, so this should
+            // never be caught
+        }
+
+        ArrayList<String> requiredPermissions = new ArrayList<>();
+        if (manifestContainsCameraPermission && !cameraPermissionGranted) {
+            requiredPermissions.add(Manifest.permission.CAMERA);
         }
 
-        if (takePicturePermission && saveAlbumPermission) {
+        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.
      *


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org