3

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?

Marta
  • 31
  • 1
  • On an Android 10 device you can use your old code if you request legacy external storage in application tag of manifest file. On an Android 11 device all public directories are usable for your old code. Deprecated functions work as always. – blackapps Apr 12 '21 at 05:02
  • You're right and I've tried that out before. It works as expected. According to the documentation, that's a temporary solution only valid for Android 10. I've opted out of this way because my given solution works just fine. It should work on Android 11 as well. But it's still marked as deprecated and could be device specific. – Marta Apr 12 '21 at 06:19
  • `According to the documentation, that's a temporary solution only valid for Android 10.` No nothing temporary. requesting legacy external storage will continue to work for Android 10 devices. – blackapps Apr 12 '21 at 06:32
  • Have you used IS_PENDING 0 while updating column .DATE_MODIFIED on Android 11? – blackapps Apr 12 '21 at 06:37
  • Thanks for dealing with the issue. You're right, the solution will work on Android 10 forever. Nevertheless, the [documentation](https://developer.android.com/training/data-storage/use-cases#opt-out-scoped-storage) advises to "temporarily opt-out of scoped storage" until the app is fully compatible. That's why I'm looking for a solution that will work in future versions, too. Setting `IS_PENDING` to 0 doesn't make a difference. I'm able to update other fields of the file (e.g. `DISPLAY_NAME`) via `ContentResolver.update(...)`. – Marta Apr 12 '21 at 12:05
  • Tried to change DATE_MODIFIED without success. – blackapps Apr 12 '21 at 12:45

0 Answers0