androidkotlinandroid-imageandroid-fileandroid-storage

Display image from content:// URI without opening file picker


As part of my app, users can select images from their device and I store the URLs to these in my database. Then, elsewhere in the app, I need to display those images.

I am getting the image URI through Intent.ACTION_PICK:

val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
imagePickerAction = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
    if (uri != null) {
        val uriString = uri.toString()
        saveToDatabase(uriString)
    }
}

This results in a URI string like content://com.android.providers.media.documents/document/image%3A1000000038.

I am able to show the image using ImageView.setImageURI at this point.

But later on, when I fetch the URI from the database and try to use setImageURI again, I get this error:

java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{cdd4e32 10499:com.skamz.shadercam/u0a160} (pid=10499, uid=10160) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs

I have also tried following the docs here https://developer.android.com/training/data-storage/shared/documents-files#open:

private fun getBitmapFromUri(uri: Uri): Bitmap {
    val parcelFileDescriptor: ParcelFileDescriptor? =
        contentResolver.openFileDescriptor(uri, "r")
    if (parcelFileDescriptor == null) {
        return bitmapFromUri(contentResolver, Uri.parse(TextureOverlayShaderData.defaultImageUrl))
    }
    val fileDescriptor: FileDescriptor = parcelFileDescriptor.fileDescriptor
    val image: Bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor)
    parcelFileDescriptor.close()
    return image
}

This returns a bitmap, which I then pass into ImageView.bitmapFromUri. Unfortunately, this produces the same error, this time coming from contentResolver.openFileDescriptor(uri, "r")


Solution

  • when I fetch the URI from the database

    That's not an option with ACTION_PICK. You cannot reliably persist a Uri that you received from ACTION_PICK and use it again later. Your access rights to the content are short-lived.

    If you switch to ACTION_OPEN_DOCUMENT and use takePersistableUriPermissions() on a ContentResolver when you get the Uri in onActivityResult(), you can safely persist that Uri. You might still run into problems using it later — for example, the user could delete the image that the Uri points to — but you will not encounter the access problem that you are seeing here.