Yalantis/uCrop

Issue on Android Q

raylee4204 opened this issue · 25 comments

Launching Ucrop with image URI from a device image on Android Q throws Permission denied error:

E/TransformImageView: onFailure: setImageUri
    java.io.FileNotFoundException: open failed: EACCES (Permission denied)
        at android.os.ParcelFileDescriptor.openInternal(ParcelFileDescriptor.java:315)
        at android.os.ParcelFileDescriptor.open(ParcelFileDescriptor.java:220)
        at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1498)
        at android.content.ContentResolver.openFileDescriptor(ContentResolver.java:1338)
        at android.content.ContentResolver.openFileDescriptor(ContentResolver.java:1286)
        at com.yalantis.ucrop.task.BitmapLoadTask.doInBackground(BitmapLoadTask.java:100)
        at com.yalantis.ucrop.task.BitmapLoadTask.doInBackground(BitmapLoadTask.java:44)
        at android.os.AsyncTask$3.call(AsyncTask.java:378)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:289)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)

This only occurs on android Q devices when the target sdk version is 29

@raylee4204 have you handled runtime permissions?
Can you provide sample code for how you are using uCrop?
Are you attempting to capture a photo or select an existing photo?

I'm on Android Q as well and I don't get a crash.

I have handled runtime permissions.

This occurs when I pass a device image's uri from my app to uCrop.

val newFile = File.createTempFile(imageFileName,  /* prefix */
     ".jpg",         /* suffix */
     storageDir   /* directory */);
val resultUri = Uri.fromFile(newFile)
UCrop
     .of(imageUri, resultUri)
     .start(activity!!, fragment)

The error looks like it's saying the file does not exist FileNotFoundException.

Does the following work for you?

//Select photo

val mediaSelector = Intent(Intent.ACTION_GET_CONTENT)
mediaSelector.setTypeAndNormalize("image/*")
fragment.startActivityForResult(mediaSelector, requestCode)

Inside onActivityResult()

var newFile = File(Environment.getExternalStoragePublicDirectory(
    Environment.DIRECTORY_PICTURES), "YourFolderNameHere")

if (!newFile.exists() && !newFile.mkdir()) {
    return 
}

newFile = File(newFile.path + File.separator + imgName)
val destinationUri = Uri.fromFile(newFile)

UCrop.of(result.data!!, destinationUri)
    .withAspectRatio(1f, 1f)
    .start(context!!, this)

Is your build target android Q?
The code I posted works on any devices below android Q

The following is from the android developers documents about scoped storage on Q:

Even with the Storage permission, such an app that accesses the raw file-system view of an external storage device has access only to the app's raw, package-specific path. If an app attempts to open files outside of its package-specific path using a raw file-system view, an error occurs:

In managed code, a FileNotFoundException occurs.

Yes, my build target is Q targetSdkVersion 29 and it works for me.

Did you use the code to select a photo

val mediaSelector = Intent(Intent.ACTION_GET_CONTENT)
mediaSelector.setTypeAndNormalize("image/*")
fragment.startActivityForResult(mediaSelector, requestCode)

Or are you using a hardcoded file path?
Note that result.data in the code snippet from my previous comment is from onActivityResult(requestCode: Int, resultCode: Int, result: Intent?).

Selecting photo via intent works fine.

I have a custom bottom sheet that shows a grid of photos located on device via following

val projection = arrayOf(
                MediaStore.Images.ImageColumns._ID,
                MediaStore.Images.ImageColumns.WIDTH,
                MediaStore.Images.ImageColumns.HEIGHT,
                MediaStore.Images.ImageColumns.ORIENTATION,
                MediaStore.Images.ImageColumns.DATE_MODIFIED,
                MediaStore.Images.ImageColumns.SIZE
            )

            val resolver = context.contentResolver
            val cursor = resolver.query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null,
                MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC"
            ) ?: return null

            val count = 0
            val images = ArrayList<ImagePickerTile>(cursor.columnCount)

            if (cursor.moveToFirst()) {
                val idColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID)
                if (idColumn < 0) {
                    cursor.close()
                    return null
                }

                do {
                    val imageLocation = Uri.withAppendedPath(
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                        cursor.getString(idColumn)
                    )
               
                    val tile = ImagePickerTile(
                        cursor.getString(idColumn),//id
                        imageLocation,//Image Uri
                        cursor.getInt(if (landscape) 2 else 1),//Width
                        cursor.getInt(if (landscape) 1 else 2)//Height
                    )
                    tile.dateModified = dateModified
                    tile.orientation = orientation
                    images.add(tile)
                } while (cursor.moveToNext() && count < maxItems)
            }

Hmm, not sure. I'm actually not in any way associated with uCrop - was just trying to help.
May need to use scoped storage. Think it's in beta still though.

This library doesn't seem to be too actively maintained although it's a great library.

I currently turned on legacy mode because of this. Although Q is still in beta, their API has been finalized. I'm hoping I can hear from the steam

@raylee4204 did you try to launch the demo app on the same device where you got an error?

I encountered this issue recently, also on Android 10. I managed to reproduce it locally and trace it through with the debugger. As @raylee4204 mentioned, the root cause does seem to be the new scoped file access in Android 10.

In particular, when dealing with a content:// based input URI, BitmapLoadTask in the library calls FileUtils.getPath, which attempts to extract the path to the file from the URI. While this may result in a path that references a valid file, Android 10 may not let the app access it due to the new scoping mechanism. This could certainly explain where the EACCES comes from.

Interestingly, this issue doesn't seem to occur if you haven't granted READ_EXTERNAL_STORAGE permission, because in that case FileUtils.getPath isn't called. Rather, the data referenced by the input URI is copied to the output URI (which is presumably backed by an accessible file), and that output URI is then used as the input URI.

It would seem that the easiest fix would be to stop using FileUtils.getPath internally to try to get the file path, and to instead either use the input URI as is (e.g. getContentResolver().openInputStream(...)) or copy the data it references to a temporary location for processing (e.g. the output URI, as per above, or an intermediate temporary file).

Disclaimer: I haven't studied the library code in depth, so I could easily be missing something.

@TermLog I installed the sample app on the same device that I was experiencing issues with when using the uCrop library with my app that targets API 29. There were no problems when selecting the same files that caused my app to crash. However, I noticed that the sample app targets API 28. When I modified it to API 29, it did crash on those same files.

Yup it throws the FileNotFoundException without legacy mode in Android Q

I can confirm as well that this worked previous on Android 8, targeting API level 29. But on Android 10, targeting API 29, it fails with the same callstack.
I added android:requestLegacyExternalStorage="true" to my app manifest, and that made uCrop work again, but that's only a temporary fix.

Is there a timeline on a fix for this?

Same issue:

  1. Google Pixel 2 with Android 10.
  2. 'compileSdkVersion 29' and 'targetSdkVersion 29'

Same issue here:

  1. Google Pixel 3 XL, Android 10
  2. compileSdkVersion 29 and targetSdkVersion 29

Any update on this issue? @GamelyAnthony solution does work, but even the documentation specifies that it should only be used while app is not compatible with scoped storage.

@Yalantis hope to adapt soon,thks!

Same issue here:

Google Pixel 3 XL, Android 10
compileSdkVersion 29 and targetSdkVersion 29

@blessedCode07
The solution is effective. I parse the media uri to Bitmap, then save the bitmap to a File in app externalCacheDir, and get the file uri by Uri.fromFile(new File(bitmapFilePath));. and last transform the uri to ucrop library.

It seems to be redundancy, but it is effective.

thks, hope it helps somebody.

Same issue here:

Google Pixel 3 XL, Android 10
compileSdkVersion 29 and targetSdkVersion 29

@HanGao1 这个不给适配android 10,目前你们咋处理的,是暂时使用legacy mode?

Same issue here:
Google Pixel 3 XL, Android 10
compileSdkVersion 29 and targetSdkVersion 29

@HanGao1 这个不给适配android 10,目前你们咋处理的,是暂时使用legacy mode?

The solution is effective. I parse the media uri to Bitmap, then save the bitmap to a File in app externalCacheDir, and get the file uri by Uri.fromFile(new File(bitmapFilePath));. and last transform the uri to ucrop library.

It seems to be redundancy, but it is effective.

thks, hope it helps somebody.

I'm now using this library. Seems to be second best in terms of design and at least it works with scoped storage. https://github.com/ArthurHub/Android-Image-Cropper

Duplicate to #598

<manifest ... >
<application android:requestLegacyExternalStorage="true" ... >
...

from: https://medium.com/@sriramaripirala/android-10-open-failed-eacces-permission-denied-da8b630a89df