Question
Is there any non-deprecated way of changing file attributes of a file my own application created on the external storage in a shared directory (not the external app folder) on an Android device running Android 10 or newer?
I can't seem to find a way of changing the file's metadata (especially the file times: created, last modified, last access) for a local file that the app creates and writes itself.
Situation
I'm creating a file, writing content to it and change the file times to an arbitary value. My old (prior to Android 10) code looks like this:
val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "foobar.txt")
FileOutputStream(file).use { outputStream ->
outputStream.write(...)
}
val fileTime = FileTime.fromMillis(1234567)
Files.getFileAttributeView(Paths.get(file.absolutePath), BasicFileAttributeView::class.java).setTimes(fileTime, fileTime, fileTime)
Problem
Android introduced scoped storage: Apps targeting Android 10 (Q, API Level 29) and up are not supposed to access the external storage and its files directly but instead use Media Store (Content Resolver).
Using Environment.getExternalStoragePublicDirectory is deprecated and could actually return wrong paths.
WRITE_EXTERNAL_STORAGE no longer provides write access when targeting Android 10+
Kind of working solution
This is an idea for my new (Android 10 and newer) solution:
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
put(MediaStore.MediaColumns.DISPLAY_NAME, "foobar.txt")
}
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)?.let { fileUri ->
contentResolver.openOutputStream(fileUri)?.use { outputStream ->
outputStream.write(...)
}
val filePath = Paths.get(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).absolutePath, filename)
val fileTime = FileTime.fromMillis(1234567)
Files.getFileAttributeView(filePath, BasicFileAttributeView::class.java).setTimes(fileTime, fileTime, fileTime)
}
It works on my Android 10 device with permission WRITE_EXTERNAL_STORAGE.
Ideas
Reading the documention and change logs, it still seems to be wrong to access the external storage directly via java.nio (Files API) in order to change the file attributes (last three lines of code), so I tried to work around and replace it with the following snippet (which should only change the last modified date):
val numRows = contentResolver.update(fileUri, ContentValues().apply {
put(MediaStore.MediaColumns.DATE_MODIFIED, 1234567)
}, null, null)
According to the documentation MediaColumns.DATE_MODIFIED directly corresponds to File#lastModified, but is
read-only and cannot be mutated.
So as wrong as it seems, it doesn't throw an exception but returns a value of 0, which means, no rows were changed.
There's maybe another way using the DocumentsContract's COLUMN_LAST_MODIFIED which I couldn't get to work either. The documentation says, all columns are read-only as well, but I've seen a few code samples mutating the value in other situations.
This question and answer do not help because they deal with another exception (the file was written by another application and its real local path is unknown).
What's the proper way of changing the file attributes of a shared file?