Android Q: Get image from gallery and process it

Parag Pawar picture Parag Pawar · Oct 24, 2019 · Viewed 7.8k times · Source

I know it seems like a very basic question, but it's specifically for Android Q.

I just want to get an image from Gallery and compress it and send to the server. But because of the Android Q's Scoped Storage, it's harder than I thought. I'll first explain what I did with code:

First I send out the intent to pick the image.

fun openGallery(fragment: Fragment){
    val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
    intent.type = "*/*"
    val mimeTypes = arrayOf("image/*")
    intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
    fragment.startActivityForResult(intent, REQUEST_IMAGE_PICK)
}

It works fine, and I'm able to get the image in the onActivityResult method

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

    if (requestCode == REQUEST_IMAGE_PICK && resultCode == Activity.RESULT_OK && null != data) {
        val selectedImage = data.data

        val source = ImageDecoder.createSource(activity!!.contentResolver, selectedImage)
        val bitmap = ImageDecoder.decodeBitmap(source)
        mBinding.circularProfileImage.setImageBitmap(bitmap)
    }
}

Okay now the question is how can I access this image in File format, so I can further process/compress it.

Following things I've tried:

val mImagePath = getImagePathFromUri(activity!!, selectedImage)

This is the path I've got:

/storage/emulated/0/DCIM/Camera/IMG_20191022_152437.jpg

I created a file from it, in the following way:

val file = File(mImagePath)

And Following is my custom logic to compress and upload image:

val mNewFile = MediaCompressor.compressCapturedImage(activity!!, file, "ProfilePictures")
uploadProfile(mNewFile)

In this custom logic, I have a method to handle sampling and rotation of the image as follows:

fun handleSamplingAndRotationBitmap(context: Context, selectedImage: File, reqWidth: Int, reqHeight: Int): Bitmap {

    val mUri = Uri.fromFile(selectedImage)

    // First decode with inJustDecodeBounds=true to check dimensions
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    var imageStream = context.contentResolver.openInputStream(mUri)
    BitmapFactory.decodeStream(imageStream, null, options)
    imageStream!!.close()

    // Calculate inSampleSize
    options.inSampleSize =
        calculateInSampleSize(options, reqWidth, reqHeight)

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false
    imageStream = context.contentResolver.openInputStream(mUri)

    var img = BitmapFactory.decodeStream(imageStream, null, options)

    img = rotateImageIfRequired(context, img!!, mUri)
    return img
}

But when I'm trying to open the stream using context.contentResolver.openInputStream I get the following error:

java.io.FileNotFoundException: /storage/emulated/0/DCIM/Camera/IMG_20191022_152437.jpg: open failed: EACCES (Permission denied)

I know I'm getting this because in Android 10 we don't have the permission to directly access files from external storage.

So, please help me figure this out, how can I use the image from external storage as a file in Android 10.

Note: I've all the required permissions, so that's not the issue

Answer

CommonsWare picture CommonsWare · Oct 24, 2019

Following things I've tried:

There is no possible reliable getImagePathFromUri() implementation.

In this custom logic, I have a method to handle sampling and rotation of the image as follows:

You do not need a File in that function. After all, your very first statement in that function goes and creates a Uri from that File. So, replace the File parameter with the Uri that you have, and skip the Uri.fromFile() call.

how can I use the image from external storage as a file in Android 10.

You can't. And, as demonstrated above, you do not need it for what you are doing.

If you find yourself in some situation where you are stuck using some library or API that absolutely positively must have a File:

  • Open an InputStream on the content, using contentResolver.openInputStream(), as you are doing today
  • Copy the bytes from that InputStream to some FileOutputStream on a file that you can read/write (e.g., getCacheDir() on Context)
  • Use your copy with the library or API that requires a File