Не отправляется файл из загрузок в 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 шт):
Единственный контракт, который (почти) гарантированно предоставляет persistable permission grant, — это OpenDocument.
PickVisualMedia может на некоторых устройствах внутри fallback-ом использовать OpenDocument, но там, где этого не происходит, persistable grant не гарантирован.
Ваши варианты:
- Перейти на использование
OpenDocument, - Остаться с
PickVisualMedia, но корректно обрабатывать случай, когда вызовtakePersistableUriPermission()завершается ошибкой.
LazyHam Я понял, почему у меня не получалось получить
persist uri: я убрал этот флаг —Intent.FLAG_GRANT_WRITE_URI_PERMISSION, и код заработал. ....CommonsWare «и код заработал» — он заработал на той конкретной модели устройства, на которой вы тестируете. Существуют и другие модели устройств. Насколько мне известно,
PickVisualMediaне задокументирован как источникpersistable- разрешений. .....