Не отправляется файл из загрузок в PDF-ридере - No persistable permission grants found for UID 10880 and Uri content://media/... -

Делаю свой PDF-ридер на kotlin+jetpack compose, скачиваю файл, пытаюсь открыть его из сторонних источников (whatsApp, telegram, мои файлы) - получаю ошибку

Process: raf.console.pdfreader, PID: 17685
java.lang.SecurityException: No persistable permission grants found for UID 10880 and Uri content://media/...
at android.os.Parcel.createExceptionOrNull(Parcel.java:3091)
at android.os.Parcel.createException(Parcel.java:3075)
at android.os.Parcel.readException(Parcel.java:3058)
at android.os.Parcel.readException(Parcel.java:3000)
at android.app.IUriGrantsManager$Stub$Proxy.takePersistableUriPermission(IUriGrantsManager.java:249)
at android.content.ContentResolver.takePersistableUriPermission(ContentResolver.java:2952)
at raf.console.pdfreader.MainActivity$onCreate$3$1$1.invoke(MainActivity.kt:158)
at raf.console.pdfreader.MainActivity$onCreate$3$1$1.invoke(MainActivity.kt:154)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.material3.SurfaceKt$Surface$1.invoke(Surface.kt:134)
at androidx.compose.material3.SurfaceKt$Surface$1.invoke(Surface.kt:115)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:380)
at androidx.compose.material3.SurfaceKt.Surface-T9BRK9s(Surface.kt:112)
at raf.console.pdfreader.MainActivity$onCreate$3$1.invoke(MainActivity.kt:151)
at raf.console.pdfreader.MainActivity$onCreate$3$1.invoke(MainActivity.kt:150)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:401)
at androidx.compose.material3.TextKt.ProvideTextStyle(Text.kt:352)
at androidx.compose.material3.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:72)
at androidx.compose.material3.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:71)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:380)
at androidx.compose.material3.MaterialThemeKt.MaterialTheme(MaterialTheme.kt:64)
at raf.console.pdfreader.ui.theme.ThemeKt.AppTheme(Theme.kt:275)
at raf.console.pdfreader.MainActivity$onCreate$3.invoke(MainActivity.kt:150)
at raf.console.pdfreader.MainActivity$onCreate$3.invoke(MainActivity.kt:149)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.ui.platform.ComposeView.Content(ComposeView.android.kt:428)
at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:252)
at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:251)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:380)
at androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:186)
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3
.invoke(AndroidCompositionLocals.android.kt:119)

MainActivity.kt

class MainActivity : ComponentActivity() {

    private val REQUEST_CODE_PERMISSIONS = 1001
    private val REQUEST_CODE_DOCUMENT = 1002
    private val REQUEST_CODE_MANAGE_STORAGE = 1003

    private val viewModel: PdfViewModel by viewModels()
    private var pendingPdfUri: Uri? = null

    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        window.setFlags(
            WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
            WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
        )

        onBackPressedDispatcher.addCallback(
            this,
            object : OnBackPressedCallback(true) {
                override fun handleOnBackPressed() {
                    viewModel.clearResource()
                }
            }
        )

        handleIncomingIntent(intent)

        intent?.data?.let { uri ->
            try {
                // Проверяем, есть ли флаг PERSISTABLE в исходном Intent
                if (intent.flags and Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION != 0) {
                    contentResolver.takePersistableUriPermission(
                        uri,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION
                    )
                } else {
                    // Для временных URI просто запрашиваем временный доступ
                    contentResolver.query(uri, null, null, null, null)?.close()
                }
            } catch (e: SecurityException) {
                // Обработка ошибки доступа
                Log.e("MainActivity", "Failed to get access to URI: $uri", e)
                // Можно показать пользователю сообщение об ошибке
            }
        }

        setContent {
            AppTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    val state = viewModel.stateFlow.collectAsState()

                    intent?.data?.let { uri ->
                        contentResolver.takePersistableUriPermission(
                            uri,
                            Intent.FLAG_GRANT_READ_URI_PERMISSION
                        )
                    }

                    Scaffold() { padding ->
                        Box(modifier = Modifier.padding(padding)) {
                            when (val actualState = state.value) {
                                null -> SelectionView()
                                is VerticalPdfReaderState ->
                                    PDFView(
                                        pdfState = actualState,
                                        onBack = { viewModel.clearResource() },
                                        onOpenDocument = { openDocumentPicker() }
                                    )
                                is HorizontalPdfReaderState ->
                                    HPDFView(
                                        pdfState = actualState,
                                        onBack = { viewModel.clearResource() },
                                        onOpenDocument = { openDocumentPicker() }
                                    )
                            }
                        }
                    }
                }
            }
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        handleIncomingIntent(intent)
        intent?.data?.let { uri ->
            try {
                if (intent.flags and Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION != 0) {
                    contentResolver.takePersistableUriPermission(
                        uri,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION
                    )
                }
                // Обновляем данные в ViewModel
                viewModel.openResource(
                    ResourceType.Remote(uri.toString(), headers = hashMapOf("" to ""))
                )
            } catch (e: Exception) {
                Log.e("MainActivity", "Error handling new intent", e)
            }
        }
    }

    private fun handleIncomingIntent(intent: Intent?) {
        intent?.data?.let { uri ->
            try {
                // 1. Попробуем определить реальный MIME-тип
                val mimeType = contentResolver.getType(uri) ?: "application/pdf"

                // 2. Если это не PDF - выходим
                if (!mimeType.equals("application/pdf", ignoreCase = true)) {
                    Toast.makeText(this, "Файл не является PDF", Toast.LENGTH_SHORT).show()
                    return
                }

                // 3. Пробуем получить доступ к файлу (даже без persistable прав)
                val inputStream = contentResolver.openInputStream(uri)
                inputStream?.use { stream ->
                    // 4. Если дошли сюда - доступ есть, можно работать с файлом
                    viewModel.openResource(ResourceType.Remote(uri.toString()))

                    // 5. Пробуем получить постоянные права (если доступны)
                    try {
                        if (intent?.flags?.and(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0) {
                            contentResolver.takePersistableUriPermission(
                                uri,
                                Intent.FLAG_GRANT_READ_URI_PERMISSION
                            )
                        } else {}
                    } catch (e: SecurityException) {
                        Log.w("MainActivity", "Cannot get persistable permissions for $uri")
                    }
                }
                    ?: run {
                        Toast.makeText(this, "Не удалось открыть файл", Toast.LENGTH_SHORT).show()
                    }
            } catch (e: Exception) {
                Log.e("MainActivity", "Error handling URI", e)
                Toast.makeText(this, "Ошибка при открытии файла", Toast.LENGTH_SHORT).show()
            }
        }
    }

    // остальной код

}

Android-Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">

  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="21" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.READ_MEDIA_DOCUMENTS" />


  <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.PDFReader" android:hardwareAccelerated="true" tools:targetApi="31">
    <activity android:name=".MainActivity" android:launchMode="singleTop" android:exported="true" android:label="@string/app_name" android:theme="@style/Theme.PDFReader">

      <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>

      <intent-filter android:priority="999">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="application/pdf" />
        <data android:scheme="file" />
        <data android:scheme="content" />
      </intent-filter>

      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="content" />
        <data android:scheme="file" />
        <data android:mimeType="application/pdf" />
      </intent-filter>

      <!-- Для открытия из файловых менеджеров -->
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="application/pdf" />
      </intent-filter>

      <!-- Для открытия по клику на PDF в браузере -->
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="http" />
        <data android:scheme="https" />
        <data android:mimeType="application/pdf" />
      </intent-filter>
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="application/pdf" />
        <data android:scheme="file" />
        <data android:scheme="content" />
        <data android:host="*" />
        <data android:pathPattern=".*\\.pdf" />
      </intent-filter>

      <meta-data android:name="android.app.lib_name" android:value="" />

    </activity>

    <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true">
      <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
    </provider>
  </application>
</manifest>

Ответы (1 шт):

Автор решения: Dev18

источник на en.SO

Единственный контракт, который (почти) гарантированно предоставляет persistable permission grant, — это OpenDocument.

PickVisualMedia может на некоторых устройствах внутри fallback-ом использовать OpenDocument, но там, где этого не происходит, persistable grant не гарантирован.

Ваши варианты:

  1. Перейти на использование OpenDocument,
  2. Остаться с PickVisualMedia, но корректно обрабатывать случай, когда вызов takePersistableUriPermission() завершается ошибкой.

LazyHam Я понял, почему у меня не получалось получить persist uri: я убрал этот флаг — Intent.FLAG_GRANT_WRITE_URI_PERMISSION, и код заработал. ....

CommonsWare «и код заработал» — он заработал на той конкретной модели устройства, на которой вы тестируете. Существуют и другие модели устройств. Насколько мне известно, PickVisualMedia не задокументирован как источник persistable - разрешений. .....

→ Ссылка