Scoped Storage Stories: MediaStore Metadata Madness
Android 10 and higher are greatly restricting access to external storage
via filesystem APIs. Instead, we need to use other APIs to work with content.
This is the 14th post in a seemingly never-ending series, where we will explore how to work with those
alternatives.
Previously, I wrote about modifying the content of other apps
and responding to a RecoverableSecurityException.
In that latter post, I wrote:
Unfortunately, that RecoverableSecurityException is not going to do you a lot of good in this case��� because DESCRIPTION cannot be modified, even if the user grants your app permission to modify the content. For some columns, MediaStore just refuses to apply the update.
It turns out that there is a recipe for making this work,
though the approach is bizarre:
First, update the content to set IS_PENDING to 1
Then, update the content to modify your desired bits of metadata and also set IS_PENDING back to 0
IS_PENDING was added in Android 10. The idea is that you set IS_PENDING to 1
to indicate that you are setting up the content, such as copying data to the Uri
supplied by the MediaStore. Apps, when querying the MediaStore, can opt out of
seeing pending items, as they may not be useful yet. Later, you flip IS_PENDING
to 0, to indicate that the content is ready for use.
Inexplicably, in an update() call on ContentResolver for MediaStore, where
you flip IS_PENDING from 1 to 0, you can also update other metadata columns
like DESCRIPTION. If IS_PENDING is not already 1, this does not work, so you
wind up having to set IS_PENDING to 1 first, then set it to 0 while updating
the rest of your metadata.
I���m sure that this system makes sense to somebody��� but not me. However, it works
on Android 10. I am still running into problems in general with MediaStore
and RecoverableSecurityException on R DP2, so I do not know if anything with
this changes there.
The v2 tagged version of my MovieLister sample
demonstrates this approach. VideoRepository uses a setMetadata() function
to update metadata using this IS_PENDING hack:
private suspend fun setMetadata(id: Long, values: ContentValues) =
withContext(Dispatchers.IO) {
val uri = ContentUris.withAppendedId(collection, id)
if (Build.VERSION.SDK_INT >= 29) {
val tempValues = ContentValues().apply {
put(MediaStore.Video.Media.IS_PENDING, 1)
}
context.contentResolver.update(uri, tempValues, null, null)
}
val updatedValues =
if (Build.VERSION.SDK_INT >= 29) {
ContentValues(values).apply {
put(MediaStore.Video.Media.IS_PENDING, 0)
}
} else {
values
}
context.contentResolver.update(uri, updatedValues, null, null)
}
You pass in the ContentValues that you wish to apply, and setMetadata():
Sets IS_PENDING to 1
Makes a new ContentValues based on your passed-in one
Adds IS_PENDING as 0 to that copy of the ContentValues
Updates the item with that augmented ContentValues copy
Only goes through all that crap on Android 10 and higher, skipping it for older devices
To me, this approach screams ���unintended side effect���, and I would not be surprised
if it breaks in Android R or future versions. Also, bear in mind that some of these
metadata values may get clobbered if the MediaStore re-indexes the content. update()
is updating the MediaStore, not metadata that might be in the media itself (e.g., EXIF
tags in a JPEG).
But, at least for Android 10, it is an option that you can consider.
The entire series of ���Scoped Storage Stories��� posts includes posts on:
The basics of using the Storage Access Framework
Getting durable access to the selected content
Working with DocumentFile for individual documents
Working with document trees
Working with DocumentsContract
Problems with the SAF API
A specific problem with listFiles() on DocumentFile
Storing content using MediaStore
Reading content from the MediaStore
Modifying MediaStore content from other apps
Limitations of MediaStore.Downloads
The undocumented Documents option
More on RecoverableSecurityException
How to modify more metadata in MediaStore


