10

I have an app which allows to select photos with an external app. Then I take the path of the photo from the uri and use it for internal actions.

When user selects a photo with Google Photo, if the picture is locally stored then the next code works perfectly. But if the picture is in the cloud the result of cursor.getString(index) is null.

I've search for some info, but not sure about the solution

final String[] projection = { "_data" };
Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);

if (cursor != null && cursor.moveToFirst()) {
    final int index = cursor.getColumnIndexOrThrow("_data");
    return cursor.getString(index);
}

Thank you!

adalpari
  • 3,052
  • 20
  • 39
  • Looks like a duplicate of this? http://stackoverflow.com/questions/30527045/choosing-photo-using-new-google-photos-app-is-broken – Sreeram Sunkara Apr 19 '17 at 15:49
  • @user2765258 I saw that post. But is not a good solution. Even the user said that accepted it because solve the problem but wasn't the complete solution he implemented. Thank you anyway – adalpari Apr 19 '17 at 15:54

2 Answers2

23

Finally and according to @CommonsWare answer and the previous post about this issue I solved getting the InputStream from the uri, coping into a new temporal file and passing the path to the function I need to use.

Here is the simplified code:

public String getImagePathFromInputStreamUri(Uri uri) {
    InputStream inputStream = null;
    String filePath = null;

    if (uri.getAuthority() != null) {
        try {
            inputStream = getContentResolver().openInputStream(uri); // context needed
            File photoFile = createTemporalFileFrom(inputStream);

            filePath = photoFile.getPath();

        } catch (FileNotFoundException e) {
            // log
        } catch (IOException e) {
            // log
        }finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    return filePath;
}

private File createTemporalFileFrom(InputStream inputStream) throws IOException {
    File targetFile = null;

    if (inputStream != null) {
        int read;
        byte[] buffer = new byte[8 * 1024];

        targetFile = createTemporalFile();
        OutputStream outputStream = new FileOutputStream(targetFile);

        while ((read = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, read);
        }
        outputStream.flush();

        try {
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    return targetFile;
}

private File createTemporalFile() {
    return new File(getExternalCacheDir(), "tempFile.jpg"); // context needed
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
adalpari
  • 3,052
  • 20
  • 39
3

When user selects a photo with Google Photo, if the picture is locally stored then the next code works perfectly.

Not necessarily. There is no requirement for that Uri to respond with a _data column to a query(). There is no requirement for the value it returns to be useful to you (e.g., a file on internal storage or removable storage that you cannot access).

If you need the photo loaded into an ImageView, pass the Uri to an image-loading library, such as Picasso.

If you need the bytes of the photo, use openInputStream() with ContentResolver to get an InputStream on the content identified by the Uri. Please open and read from the InputStream on a background thread.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Thank you, but the thing is that I need the real path due to internal requirements. I'm not going to show or manipulate the image, just pass the path to a backend service that it's already implemented. – adalpari Apr 19 '17 at 15:56
  • 2
    @adalPaRi: There is no "real path". If you need a file, you are welcome to make a copy of the photo, by using the `InputStream` from `openInputStream()` with a `FileOutputStream` to some file that you control. – CommonsWare Apr 19 '17 at 16:04
  • @CommonsWare, I am having a problem with Google Photos here. I was using Picasso to load the URI into images, and passing the URI between my activities but after some searching I discovered it seems the URI becomes invalid as soon as I leave the `onActivityResult` where I retrieve the intent from Gallery/Camera `ACTION_PICK` Intent chooser. What do you suggest? Any links for recommendation? – rgoncalv Mar 14 '18 at 13:54
  • @rgoncalv: You need to use `FLAG_GRANT_READ_URI_PERMISSION` whenever passing a `Uri` between components, including your own components. See [this blog post](https://commonsware.com/blog/2016/08/10/uri-access-lifetime-shorter-than-you-might-think.html) for more. – CommonsWare Mar 14 '18 at 14:02
  • I tried this (read your blog post): When coming from Activity A back to Activity B, I add `picIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)` before `setResult(Activity.RESULT_OK, savedPicIntent)`, in a method inside Activity A. I can access the picture perfectly in Activity B (I see the picture in a small frame in Activity B and can go to another Activity C to see the pic on full screen, all as expected), but when sending the pic to the server (using Firebase), in Activity B, the crash happens on this line: ` .putFile(chosenImageUri!!, metadata)` – rgoncalv Mar 14 '18 at 14:46
  • @CommonsWare, Here it is the full Firebase method: `val metadata = StorageMetadata.Builder().setContentType("image/jpeg").build() Log.d("TAG", "BEFORE CRASH") val uploadTask = FirebaseStorage.getInstance().reference .child("requests_images") .child(FirebaseAuth.getInstance().currentUser!!.uid) .putFile(chosenImageUri!!, metadata` – rgoncalv Mar 14 '18 at 14:47
  • @rgoncalv: I have not tried the `setResult()` approach and so I don't know what the rules are there. Beyond that, I suggest perhaps asking a separate Stack Overflow question, with your code and the full Java stack trace as part of a [mcve]. – CommonsWare Mar 14 '18 at 14:49