33
votes

I'm trying to set the source of an img tag in my app based on the image chosen from the device image gallery using the PhoneGap/Cordova Camera Plugin.

It has worked previously as intended on older versions of Android (3.3) and works fine on iOS but now fails to resolve the image path on 4.4 (KitKat).

The returned path for the returned image url looks something like:

content://com.android.providers.media.documents/document/image%3A352

When I use this path to set as the image src via JavaScript, the URL cannot be resolved and therefore produces a load error. There is no issue when taking a picture with the camera, it only seems to occur when choosing an existing picture from the gallery.

I have tried encoding to base64 and also tried the method mentioned in the docs resolveLocalFileSystemURI(); but I have had no luck with these. I've also tried removing the camera plugin and rebuilding the app but no joy.

My guess is that something has changed with the way KitKat handles the gallery and PhoneGap/Camera plugin haven't been updated to accomodate for this yet.

6
and what about content_type? how to have it also?brauliobo

6 Answers

16
votes

Something broke in Android 4.4 with the URI encoding of images.

A bug has been filed against Cordova here: https://issues.apache.org/jira/browse/CB-5398

In the docs for getPicture, under the Android Quicks section, it discusses this problem and points to a StackOverflow question with a workaround (edit the Camera plugin java code to force it to open the Gallery app instead of the Storage Access Framework app.)

It seems another thing you could do is set the destination type to DATA_URL.

19
votes

A kind of really-very-dirty workaround works for me while this bug is fixed. Use in case of extreme necessity :)

if (imageURI.substring(0,21)=="content://com.android") {
  photo_split=imageURI.split("%3A");
  imageURI="content://media/external/images/media/"+photo_split[1];
}
4
votes

Adobe assures me that that this problem will be fixed in 3.5.0. It is not fixed in 3.4. As 3.5.0 is scheduled to be release in mid-May, I'm just going to wait until then.

Adobe claims that this was not a change that could be made on the plugin level. It was a basic change in the cordova code. It's too bad it took so long for them to come up with this fix.

UPDATE: Cordova 3.5.0 was released on May 9. You can download it bia node and see if the problems are in fact solved.

4
votes

Here is a simple fix to this problem:

replace this:

content://com.android.providers.media.documents/document/image%3A352

by this:

content://com.android.providers.media.documents/document/image%253A352

if you are using JavaScript you can use this code:

var path = content://com.android.providers.media.documents/document/image%3A352;
path = path.replace("%", "%25");

this technique force the uri to let pass "%3A" as it is, without changing it to ":", hope it will work for you !

2
votes

Not much more robust just a few more checks for special conditions like "content:" with no extensions and so. Also, since I need to upload it, I am detecting the extension, or create a .jpg extension if the file does not have one:

// Android 4.4 cordova workarounds ... returns new kind or URL for content from chooser
                //if (imageUrl.substring(0,21)=="content://com.android") {
                if(imageUrl.indexOf('content://') != -1 && imageUrl.indexOf("%3A") != -1){
                    //"PlainFileUrl = content://com.android.providers.media.documents/document/image%3A14",
                    photo_split=imageUrl.split("%3A");
                    imageUrl="content://media/external/images/media/"+photo_split[1];
                }
                // workaround end

                var fileName = imageUrl.substr(imageUrl.lastIndexOf('/') + 1);
                var extension;

                // check for content: protocol to make sure is not
                // a file with no extension
                if (imageUrl.indexOf('content://') != -1) {
                    if(imageUrl.lastIndexOf('.') > imageUrl.lastIndexOf('/')){
                        extension = imageUrl.substr(imageUrl.lastIndexOf('.') + 1);
                    }else{
                        extension = "jpg";
                        fileName = fileName + ".jpg";
                        LogService.log("Created File Extension jpg");
                    }
                } else {
                    if (imageUrl.lastIndexOf('.') == -1 || (imageUrl.lastIndexOf('.') < imageUrl.lastIndexOf('/')) ) {
                        extension = "invalid";
                    } else {
                        extension = imageUrl.substr(imageUrl.lastIndexOf('.') + 1);
                    }
                }
0
votes

Whenever some uri is passed into <img src="uri" /> it's implicitly decoded from

content://com.android.providers.media.documents/document/image%3A9888 (1)

into

content://com.android.providers.media.documents/document/image:9888 (2)

However, after returning from Intent.ACTION_OPEN_DOCUMENT or Intent.ACTION_GET_CONTENT Android provides you with the read permission for (1), not (2). In this case WebView will expectantly log an error:

java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaDocumentsProvider uri content://com.android.providers.media.documents/document/image:9888 from pid=13163, uid=10165 requires android.permission.MANAGE_DOCUMENTS, or grantUriPermission()

or

Unable to open content URL

Code snippet

All you need to resolve the issue is

    String uriToUseInWebView = transformForWebView(uri.toString());

    private String transformForWebView(String uri) {
        for (int i = 0; i < timesDecodingWebView(); i++)
            uri = uri.replace("%", Uri.encode("%"));
        return uri;
    }

    private int timesDecodingWebView() {
        if (Build.VERSION.RELEASE.equals("4.4.2")) {
            return 2;
        } else {
            return 1;
        }
    }

in your Java code before passing uri into HTML/JS to ensure that (1) will be actually loaded.

I've tested that on 4.4.2, 4.4.4 and 5.0. The funny part is that Android 4.4.2 decodes the uri internally twice.