Take Persistable URI Permission for ACTION_SEND intent filter uri results in SecurityException
Asked Answered
T

2

5

My users can select files via ACTION_OPEN_DOCUMENT

val launcher = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.OpenDocument(),
    onResult = onResult
)

After which I use takePersistableUriPermission

contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)

This works great! Now the picker UI is not what I'd call optimal, so I want to also allow inverting the control flow by sharing the file with my app.

<intent-filter>
   <action android:name="android.intent.action.SEND" />
   <category android:name="android.intent.category.DEFAULT" />
   <data android:mimeType="image/*" />
</intent-filter>

And in onCreate or onNewIntent in my Activity, I want to also take the persistable URI permission.

override fun onNewIntent(intent: Intent?) {
    // null checks etc
    val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri ?: return
    contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
    // ...
}

This however, results in a SecurityException:

java.lang.SecurityException: No persistable permission grants found for UID 10146 and Uri content://com.google.android.apps.photos.contentprovider/-1/1/content://media/external/images/media/61/REQUIRE_ORIGINAL/NONE/image/jpeg/702648108

Is there a way to achieve this?

Triphibious answered 24/10, 2021 at 16:47 Comment(5)
Why would you take persistable permission? I think it is not offered to your app to begin with. Did you check the flags if they were there in the received intent?Bacitracin
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); That is not the way to do it. Please look at examples. First look in the offered flags if that flag is there.Bacitracin
I have to get long-term access to the file to display it inside my app. I don't want to necessarily copy it into my apps internal storage, if I can avoid it. ` I think it is not offered you to begin with`, yes that is the problem. Can I request that a) after receiving the URI or b) before receiving the URI?Triphibious
No you cannot request that.Bacitracin
Wow, that's unfortunate..Triphibious
D
9

Is there a way to achieve this?

Generally, no. takePersistableUriPermission() is for Storage Access Framework Uri values, as in your ACTION_OPEN_DOCUMENT scenario. It is not for arbitrary Uri values, like you might get via ACTION_PICK, or EXTRA_STREAM on receiving ACTION_SEND, or the Uri passed to ACTION_VIEW, etc.

For ACTION_SEND, ACTION_VIEW, etc., you need to assume that your app has only transitory access to the content. You would need to have some sort of "import" operation and make your own copy of that content if you need longer-term access.

Diannadianne answered 24/10, 2021 at 17:16 Comment(1)
Yes, I expected as much, thank youTriphibious
U
0

I use UriToFile utility to cache and use file from content provider.

val file = UriToFile(context).getImageBody(conetntUri)

Utility:

import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream

class UriToFile(context: Context) {
    private val applicationContext = context.applicationContext
    fun getImageBody(imageUri: Uri): File {
        val parcelFileDescriptor = applicationContext.contentResolver.openFileDescriptor(
            imageUri,
            "r",// r stands for read only for the file
            null
        )
        val file = File(
            applicationContext.cacheDir,
            applicationContext.contentResolver.getFileName(imageUri)
        )
        val inputStream = FileInputStream(parcelFileDescriptor?.fileDescriptor)
        val outputStream = FileOutputStream(file)
        inputStream.copyTo(outputStream)
        parcelFileDescriptor?.close()
        return file
    }
}

private fun ContentResolver.getFileName(uri: Uri): String {
    var name = ""
    val cursor = this.query(
        uri, null, null,
        null, null
    )
    cursor?.use {
        it.moveToFirst()
        name = it.getString(it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
    }
    return name
}
Unpaid answered 12/6 at 13:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.