Reputation: 425
The idea is this:
The idea is to prevent any permission requests since SAF does not need permissions for accessing a single file. The temporary permissions are also persisted so next time the user opens the app, they don't need to do anything, allowing for a seamless experience.
However, I'm having some trouble persisting this permission. I'm thinking it has something to do with the way the URI is stored (it must be converted to a string before it can be written as a byte array), so maybe the permissions information is lost?
Right now, the user is able to select a file, but when the app is restarted and they try to load the file again, the app crashes due to java.lang.SecurityException: Permission Denial: opening provider com.android.externalstorage.ExternalStorageProvider from ProcessRecord{...} requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
My code right now looks like this:
[package and imports...]
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
AppTheme {
FileSelect() // dialog for selecting a file
}
}
}
}
@Composable
fun FileSelect() {
// State variables
val context = LocalContext.current
var selectedFileUri by remember { mutableStateOf<Uri?>(null) }
var fileContent by remember { mutableStateOf<String?>(null) }
// Open file from URI, set fileContent, and write URI to internal storage
val openFile = { uri: Uri? ->
selectedFileUri = uri
uri?.let {
fileContent = readTextFromUri(context, it)
try {
takePersistableUriPermission(context, it)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
// Try writing to internal storage for future use
try {
val fos: FileOutputStream = context.openFileOutput("URILocation.txt", Context.MODE_PRIVATE)
fos.write(selectedFileUri.toString().toByteArray()); fos.flush(); fos.close()
Log.d("MainActivity", "Write URILocation.txt - success")
} catch (e: IOException) {
// Error writing to URILocation.txt
Log.d("MainActivity", "Write URILocation.txt - failure")
e.printStackTrace()
}
}
// Launch file selector
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent(),
onResult = { uri: Uri? -> openFile(uri) }
)
// Handle select file
fun buttonOnClick(){
try {
// Try reading URILocation.txt
val fin: FileInputStream = context.openFileInput("URILocation.txt")
Log.d("MainActivity", "Read URILocation.txt - success")
var a: Int; val temp = StringBuilder(); while (fin.read().also { a = it } != -1) { temp.append(a.toChar()) }
val uriS = temp.toString() // URI String
fin.close()
// URI URI Object
val uri: Uri = Uri.parse(uriS)
// Read file at URI
openFile(uri)
} catch (e: IOException) {
// If no URILocation.txt, launch file picker
launcher.launch("*/*")
}
}
Surface {
Column {
// Select file button
Spacer(modifier = Modifier.height(100.dp))
Button(onClick = {
buttonOnClick()
}) {
Text(text = "Select or Load File")
}
// Display file contents
selectedFileUri?.let { uri ->
Text(text = "Selected file: $uri")
fileContent?.let { content ->
Text(text = content)
[Do something with the content...]
}
}
}
}
}
// Takes file URI -> returns nullable string of content
fun readTextFromUri(context: Context, uri: Uri): String? {
return context.contentResolver.openInputStream(uri)?.use { inputStream ->
BufferedReader(InputStreamReader(inputStream)).use { reader ->
reader.readText()
}
}
}
// Grant persistable permissions to URI
fun takePersistableUriPermission(context: Context, uri: Uri) {
val contentResolver: ContentResolver = context.contentResolver
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
// Check if the URI can be granted persistable permissions
val takeFlagsSupported = DocumentsContract.isDocumentUri(context, uri)
if (takeFlagsSupported) {
try {
contentResolver.takePersistableUriPermission(uri, takeFlags)
} catch (e: SecurityException) {
e.printStackTrace()
}
} else {
// Handle the case where the URI does not support persistable permissions
Log.d("MainActivity","Persistable permissions not supported for this URI: $uri")
}
}
Is it even possible to do it this way or am I missing something? Thanks!
Upvotes: 1
Views: 241
Reputation: 1007544
Switch ActivityResultContracts.GetContent()
to ActivityResultContracts.OpenDocument()
. GetContent
is not part of SAF; you cannot get persistable Uri
permissions using GetContent
.
Upvotes: 1