4

I need help with this problem since I'm new to Android.

My app support JellyBean (16) up to Oreo (26).

I have an UploadService that requires openInputStream() to upload data because the new behavior in Nougat.

This code works fine in Marshmallow and below, but always give me SecurityException crash on Nougat. And it crashes on the line where openInputStream() is called with error:

java.lang.SecurityException: Permission Denial: reading com.miui.gallery.provider.GalleryOpenProvider uri content://com.miui.gallery.open/raw/%2Fstorage%2Femulated%2F0%2FDCIM%2FCamera%2FIMG_20171008_182834.jpg from pid=30846, uid=10195 requires the provider be exported, or grantUriPermission()

The file uri could be from various app (gallery, camera, etc). I've narrowed down the problem to uri that comes from ACTION_GET_CONTENT intent (anything that comes from camera intent or MediaRecorder works fine).

I think it's because the uri lost its permission when passed into the service, but adding Intent.FLAG_GRANT_WRITE_URI_PERMISSION and Intent.FLAG_GRANT_READ_URI_PERMISSION doesn't help.

Also tried adding FLAG_GRANT_PERSISTABLE_URI_PERMISSION flag, but it still crashes and getContentResolver().takePersistableUriPermission() causes another SecurityException crash saying the said uri hasn't been granted persistable uri...

UploadService.java

  //.......... code to prepare for upload

  if ( contentResolver != null && schemeContentFile ) {
            mMime = UtilMedia.getMime(this, uri);

            try {
                InputStream is     = contentResolver.openInputStream(uri);
                byte[]      mBytes = getBytes(is);

                Bundle fileDetail = UtilMedia.getFileDetailFromUri(this, uri);

                Log.d("AndroidRuntime", TAG + " " + mMime + " " + UtilToString.bundleToString(fileDetail) + " imageFile " + mFile);

                currTitle = fileDetail.getString(OpenableColumns.DISPLAY_NAME, "");
                MediaType type = MediaType.parse(mMime);
                requestFile = RequestBody.create(type, mBytes);

            } catch ( IOException e ) {
                e.printStackTrace();
            } catch ( SecurityException e ) {
                e.printStackTrace();
            }
        }

  //............continue to upload

Thank You in advance.

EDIT (Additional Info) In case this is important. The activity calling the service is calling finish() after it sends all the required data to the service, letting user to use the app, while the upload resumed in the background (with notification to tell user). And also, the upload works based on queue, and user can choose to upload multiple files in the activity. The first file always gets uploaded, but the files after always return with the crash.

ayakashi-ya
  • 261
  • 3
  • 18
  • Some devices do not allow permissions to read file from camera or picture directory. Does the problem exist if you upload file from a different directory? – Nabin Bhandari Oct 11 '17 at 09:20
  • Thanks for the suggestion! I don't have the Nougat device right now, but I'll try it out when I do. Oh, and I can get the mime just fine (the function also uses ContentResolver and the same uri), does it mean that read/write uri permission is enabled at the time, but somehow it needed other permission to read file directly? – ayakashi-ya Oct 11 '17 at 09:33
  • So, I tried your suggestion @NabinBhandari, but it still give me the same exception. I also edited my post to add more info just in case. – ayakashi-ya Oct 11 '17 at 10:32
  • I have this problem for Xiaomi devices. I don't understand why the permission for reading from the Uri isn't persistent like other gallery providers. In my case I am trying to open the Uri from an activity started by the one that actually received the Uri. With the standard Android providers it works. @NabinBhandari is there any way to get a permission that can be propagated with GET_CONTENT request? Or should I switch to OPEN_DOCUMENT? Per documentation `Use ACTION_OPEN_DOCUMENT if you want your app to have long term, persistent access to documents owned by a document provider` – androidguy Jan 27 '18 at 04:27

2 Answers2

11

I finally managed to fix this. Apparently it is because the permission for the given uri is only valid as long as the receiving activity is active. So, sending the uri to a background service (upload service) will result in SecurityException as expected, unless the uri is a persistable uri (ie. from ACTION_OPEN_DOCUMENT).

So my solution is to copy the file to a file my app created and use FileProvider.getUriForFile() function to get the uri and send it instead to the background service and delete the copy when my service finished uploading. This works fine even after the calling activity has finished.

ayakashi-ya
  • 261
  • 3
  • 18
1

The following code is working fine in my device (android 7):

public void pickImage(View view) {
    try {
        Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);
        photoPickerIntent.setType("image/*");
        photoPickerIntent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
        startActivityForResult(photoPickerIntent, RC_PICK_IMAGE);
    } catch (ActivityNotFoundException e) {
        e.printStackTrace();
    }
}

onActivityResult:

Uri uri = data.getData();
InputStream in = getContentResolver().openInputStream(uri);
Nabin Bhandari
  • 15,949
  • 6
  • 45
  • 59
  • Hi, so it works when on activityresult. But i need to send the uri to the UploadService (background service) from the activity. It's only successful with the first item, strangely. Changed my `ACTION_GET_CONTENT` to `ACTION_OPEN_DOCUMENT` with persistable uri permission and it works fine in the service for all the uri sent, so it definitely something with the uri itself, or the permission for the uri. How can I pass this permission to the service? As I stated in my question, I already added the read/write uri flag to the intent to the service but it did nothing... Thank you. – ayakashi-ya Oct 12 '17 at 03:45
  • I am not sure if it is a good idea, but you can convert the Uri to a path, and pass the path to your service. – Nabin Bhandari Oct 12 '17 at 04:03
  • Nougat will throw another exception if I send the path of the content uri from `ACTION_GET_CONTENT` (been there). But maybe, if I copy the file `onActivityResult()` to a file my app control and use that file uri/path instead, it'll work given uri from camera intent works fine. I'm going to try this out and post here the result. Thanks :) – ayakashi-ya Oct 12 '17 at 04:12