SimpleStorage icon indicating copy to clipboard operation
SimpleStorage copied to clipboard

MediaStore lost access to files after uninstalling the app or clearing its data

Open TurKurT656 opened this issue 3 years ago • 7 comments

Library version: 1.5.1 OS version: [Android 12] Device model: [Emulator Pixel 5]

Describe the bug I'm creating a txt file in Downloads folder with this code:

fun writeToConfig() {
    val file = MediaStoreCompat.createDownload(
        context,
        FileDescription("config-test.txt", "", "text/plain"),
        CreateMode.REUSE,
    )
    file?.openOutputStream(true).use {
        it?.write("HelloWorld\n".toByteArray())
    }
}

And I'm reading the file with this approach:

fun readConfig() {
    val file = MediaStoreCompat.fromFileName(context, MediaType.DOWNLOADS, "config-test.txt")
    file?.openInputStream()?.use {
        val text = it.readBytes().toString(Charsets.UTF_8)
        logD(text)
    }
}

Everything works fine. But after I uninstall the app and reinstall it, when I call the readConfig() function, the file variable is null. and strange thing is that when i call the writeToConfig() function again, instead of reusing the same file it creates a new file with this name: "config-test (1).txt". So after I saw this, I tried to retest the code and I deleted the files (""config-test.txt" and "config-test (1).txt"). After force stopping and relaunching the app I got an Exception: android.database.sqlite.SQLiteConstraintException. After this exception you cant work with this code anymore and the only way to fix this crash is that you need to wipe data from your device settings. So we have two bugs in here

To Reproduce

  1. Use MediaStoreCompat to write/read a file in Downloads folder.
  2. Unistall the app
  3. Install the app
  4. (Bug 1 appears)
  5. Go to Downloads folder and manually delete the file.
  6. Force stop the app
  7. launch the app again.
  8. (Bug 2 --> Crash)

Stacktrace

FATAL EXCEPTION: main
Process: app.source.getcontact, PID: 8835
java.lang.RuntimeException: Unable to create application com.test.myapp.DebugApp: android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: files._data (code 2067 SQLITE_CONSTRAINT_UNIQUE)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6730)
at android.app.ActivityThread.access$1500(ActivityThread.java:247)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2053)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Caused by: android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: files._data (code 2067 SQLITE_CONSTRAINT_UNIQUE)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:178)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142)
at android.content.ContentProviderProxy.insert(ContentProviderNative.java:557)
at android.content.ContentResolver.insert(ContentResolver.java:2193)
at android.content.ContentResolver.insert(ContentResolver.java:2155)
at com.anggrayudi.storage.media.MediaStoreCompat.createMedia(MediaStoreCompat.kt:143)
at com.anggrayudi.storage.media.MediaStoreCompat.createDownload(MediaStoreCompat.kt:34)
at com.test.myapp.ConfigCache.writeToConfig(ConfigCacheImpl.kt:37)
at com.test.myapp.DebugApp.onCreate(DebugApp.kt:7)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1211)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6725)
at android.app.ActivityThread.access$1500(ActivityThread.java:247) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2053) 
at android.os.Handler.dispatchMessage(Handler.java:106) 
at android.os.Looper.loopOnce(Looper.java:201) 
at android.os.Looper.loop(Looper.java:288) 
at android.app.ActivityThread.main(ActivityThread.java:7839) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003) 

TurKurT656 avatar Nov 07 '22 15:11 TurKurT656

I'm also able to reproduce these two issues. But, if you try to use another name, e.g. another-config-test.txt, then the error will be disappear. These are OS level issues, not the library. I presume the OS is trying to insert the same file name into SAF database, but the previous name still exists in the database even though the actual file was deleted, hence the unique constraint is not satisfied.

FYI, if you uninstall the app, then all of your files created with MediaStore will be unbound from your app. I don't recommend anyone to use MediaStore if you want to read the files again in the future. Use DocumentFile with storage access instead. MediaStore is only appropriate for write and forget cases, for example saving screenshots and receipts. Additionally, to avoid this crash on your app, generate unique file name for every file creation. You can append random string, integer, or timestamp to your file name.

This issue can't be fixed from my side, but I'll leave it open until Android team resolve it. I'll raise this issue on Google Issue Tracker and will put the link here soon.

anggrayudi avatar Nov 28 '22 17:11 anggrayudi

I'm trying to implement a ConfigHelper (a configuration file that holds list of key value pairs) that survives from uninstalling app. As described https://developer.android.com/training/data-storage I can only use MediaStore (SharedPreferences doesn't survive from uninstall and SAF always ask from user to choose a file). So I cannot use DocumentFile because I cannot ask from user to select a file that I need to do some configurations for the app. Actually this ConfigHelper is more like SharedPreferences with the ability of surviving from uninstall.

TurKurT656 avatar Nov 30 '22 11:11 TurKurT656

I didn't find any article about ConfigHelper, can you post the link here @TurKurT656?

anggrayudi avatar Dec 01 '22 03:12 anggrayudi

@anggrayudi Its my own custom class that behaves like SharedPrefrences. I've named it ConfigHelper its not a public library or something :). Sorry if I confused you

TurKurT656 avatar Dec 07 '22 10:12 TurKurT656

I encountered the same issue, as discussed in https://github.com/lolo-io/OneList/issues/41#issuecomment-1370501337 . The problem is that DocumentFile and DocumentFileHelper never allow to get access to any file whatsoever. I actually reused a helper function that @anggrayudi you mentioned elsewhere, I guess you will recognize what I mean if you see my implementation:

https://github.com/lrq3000/OneList/blob/master/app/src/main/java/com/lolo/io/onelist/util/Utils.kt#L110

This helper function tries to use a DocumentFile, and if it fails, it uses a MediaStore to create or reuse a file. I noticed that in practice, it always uses a DocumentFile. All my attempts at using a DocumentFile manually just miserably failed:

https://github.com/lrq3000/OneList/blob/master/app/src/main/java/com/lolo/io/onelist/dialogs/StorageDialog.kt#L74

I will ask another related question elsewhere, but the culprit is that it appears that all the DocumentFile and DocumentFileCompat functions are now broken. So this issue needs fixing.

lrq3000 avatar Jan 04 '23 05:01 lrq3000

ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION can solve your problem from api 30 and above

ngminhkhoa avatar Mar 23 '23 05:03 ngminhkhoa

Your app will be refused from Play Store if you use this permission.

23 mars 2023 06:13:50 Nguyá»…n Minh Khoa @.***>:

ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION can solve your problem from api 30 and above

— Reply to this email directly, view it on GitHub[https://github.com/anggrayudi/SimpleStorage/issues/103#issuecomment-1480613368], or unsubscribe[https://github.com/notifications/unsubscribe-auth/AAIRFXSZ4P4TEXHCIXONITDW5PLY5ANCNFSM6AAAAAARZKBZP4]. You are receiving this because you commented.[Image de pistage][https://github.com/notifications/beacon/AAIRFXQ3DFP2MEADZ3KJPKTW5PLY5A5CNFSM6AAAAAARZKBZP6WGG33NNVSW45C7OR4XAZNMJFZXG5LFINXW23LFNZ2KUY3PNVWWK3TUL5UWJTSYIBO7Q.gif]

lrq3000 avatar Mar 23 '23 08:03 lrq3000