33

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.

ProgramFOX
  • 5,352
  • 10
  • 40
  • 48
CrazyEraserUK
  • 353
  • 1
  • 3
  • 11

6 Answers6

19

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];
}
Miguel Delgado
  • 443
  • 3
  • 9
  • 2
    I like this.. "content://com.android" does not work in every case, since when I use ES File Explorer I get a "content://com.esstrong..." ... so there needs to be more logic built in, but the overall workaround with recreating the old style URL does it well for our app from Android 4.0 up to 4.4. – christianmenkens Feb 25 '14 at 21:41
  • 3
    @christianmenkens did you come up with something more robust? I'd like to use it if you post it. Otherwise I think everyone is waiting for the fix to be delivered in cordova 3.5.0 – Harry Moreno Mar 21 '14 at 04:23
  • and what content_type, how to have it? – brauliobo Apr 21 '15 at 19:43
  • 3
    For what it's worth, this bug still exists for me in Cordova 5.3.3, and this workaround still fixes it. – Chris Rae Nov 14 '15 at 19:51
  • still exists for me in Cordova 9.0.0 – Doctor.Who. Mar 02 '21 at 01:18
16

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.

MBillau
  • 5,336
  • 2
  • 25
  • 29
  • 1
    Thanks for your response. I managed to get it working by setting the destination type to DATA_URL as you suggested. I guess it's a bit more memory intensive by using this method but it seems to work well. – CrazyEraserUK Dec 18 '13 at 10:42
  • 1
    @XigenBen, yes using DATA_URL will use up more memory. In fact, I have seen apps that crash when you try to select an image from Google Drive with DATA_URL because the image encoded as a DATA_URL is way to big to pass across the Cordova bridge. Cordova 3.4 came out two weeks ago and has fixed the above issue, so you should be able to upgrade and stop using DATA_URL. – MBillau Mar 21 '14 at 13:10
  • but the contenttype is also needed, and with base64 I can't have it :( – brauliobo Apr 21 '15 at 19:41
4

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.

MLU
  • 181
  • 1
  • 6
4

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 !

Nourdine Alouane
  • 763
  • 11
  • 19
2

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);
                    }
                }
christianmenkens
  • 782
  • 4
  • 22
0

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.

riwnodennyk
  • 7,312
  • 4
  • 31
  • 36