apache / cordova-plugin-file

Apache Cordova File Plugin
https://cordova.apache.org/
Apache License 2.0
740 stars 757 forks source link

Migrated Android device, app can't access folders created before the migration #599

Closed rolinger closed 8 months ago

rolinger commented 8 months ago

Bug Report

  1. My app has been using cordova.file.externalRootDirectory + "Download/MyAppDir" to write files since Android 9 or 10 and it has all worked, but now suddenly on Android 13 it has stopped when a device was migrated from Device A to Device B. Device A was a busted phone, and all the content/apps/folders, etc was migrated to the new Device B.
cordova.file.externalRootDirectory =  file:///storage/emulated/0/
      Images/
      Documents/
      Download/
            MyAppDir/ (if not exist, create)
                  aFiles/    (if not exist, create)
                       a-hello.doc  (file downloaded from app)
                       a-goodbye.pdf (file downloaded from app)
                       a-presentation.ppt (file downloaded from app)
                       ...
                  bFiles/    (if not exist, create)
                       b-hello.doc  (file downloaded from app)
                       b-goodbye.pdf (file downloaded from app)
                       b-presentation.ppt (file downloaded from app)
                       ...
                  cFiles/
                  .....

Via the UIs File Manager app, you can browse to the Downloads folder and then into the MyAppDir folder and can access sub-folders and all the downloaded files. This is the case before and after the migration - all is good so far.

But after the migration to Device B, my app can no longer download to the MyAppDir folder or any of its subfolders. My app was receiving a permission denied error

So I start troubleshooting thinking maybe Android 13 now requires permissions to access this area. And after multiple fruitless tests, I deleted one of the sub-folders (aFiles)...then in my app tried to download a file that would need the aFiles folder. The app successfully created a new aFiles folder AND successfully downloaded the file again.

I ran this test multiple times across the other various sub-folders and was able to replicate it problem every time. permission denied - then I deleted to the folder, tried the download again, it recreated the folder and successfully downloaded the file. My app created all these folders, why couldn't my app access them again until after I deleted them and redownloaded? Even more odd though...is I only had to delete the MyAppDir subfolders to get this to work. I didn't have to delete the main MyAppDir folder at all.

I can't have users deleting folders and redownloading content again. I think during the upgrade or migrating the phone the device somehow lost the reference that those folders were created by my app and thus wouldn't allow me to download to them again. When manually deleting them and then recreating them, its like the association was restored and new permissions were granted.

How can I account for such issues?

On a side note:

What directory does cordova.file.externalDataDirectory actually write to? I can download files and write to it with no problem, there no permissions issues. But then on the users phone, via FileManager, you can't find the file anywhere...where is it being written to and how can the users "File" app locate the downloaded file?

breautek commented 8 months ago

I have a PR to expand on the docs. Android's external storage (all cordova.file.external* constants) have several limitations especially surrounding Filesystem APIs.

https://github.com/apache/cordova-plugin-file/pull/593/files#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5R181

TL;DR; you'll probably need to migrate to use a media-storage plugin, as hinted in my documentation PR.

Basically on any external storage partition access is limited to:

  1. Read/write to files that is owned/created by your own app
  2. Reading media files created by other apps with the appropriate permission. Media files are limited to Audio, Video, and Images. Pre-API 33 requires READ_EXTERNAL_STORAGE permission, while API 33 and later requires READ_MEDIA_AUDIO, READ_MEDIA_VIDEO, andREAD_MEDIA_IMAGES` permission respectively.
  3. Other kinds of files such as document files are NOT readable using any filesystem API unless if the file is owned by your application.

Additionally, file API access to external storage requires API 30+. These Scoped storage limitations however are effective on API 29, so specifically on API 29 external storage is not accessible via filesystem APIs. For these reasons, if you use external storage, you'd probably be better to migrate to using a plugin that interfaces with the media store API, which allows for temporary read/write access to specific files as needed.

The File Manager app can see and probably even read/write to those files because it would have the MANAGE_EXTERNAL_STORAGE permission, which is a protected and justified permission. If you declare and try to use this permission, google will expect you to justify why you need it, which is generally reserved for file managers or anti-virus like apps. (Paraphrasing from google docs)

It sounds like in your case, your app created some kind of folder structure of data files inside external storage, and after "migration" from one device to another, these files probably got created by the app responsible for the migration, and thus is no longer owned by your app, which due to scoped storage, read/write access is blocked by filesystem APIs. This is also why the issue "corrects" itself if these data files are deleted and the app redownloads and stores them, since it's the app that's doing the writing, not your migration app/process.

What directory does cordova.file.externalDataDirectory actually write to?

It depends.

If the device has a physical storage device attached, then this is usually /sdcard/<app-id>/files (Not 100% sure on the full path but the /sdcard/ is right)

If the device has no physical storage device attached, then the path is generally /storage/emulated/0/<app-id>/files

0 directory refers to the user id, which is usually 0, but if you're using a corporate multi-user device, then this number might be different.

breautek commented 8 months ago

Closing because I don't believe there is anything actionable by Cordova.

Google has [warned](https://developer.android.com/about/versions/11/privacy/storage#migrate-data-for-scoped-storage] that data migration might be necessary before Scoped Storage becomes into full effect. Unfortunately the time for this is long passed.