ionic-team / capacitor-plugins

Official plugins for Capacitor ⚡️
503 stars 574 forks source link

feat(android): update the filesystem plugin to accommodate scoped storage and permission changes since Android 10 #169

Open carlpoole opened 3 years ago

carlpoole commented 3 years ago

Feature Request

Plugin

Filesystem

Description

Update the filesystem plugin to utilize the new MANAGE_EXTERNAL_STORAGE permission when necessary (apps targeting Android 11, API 30). Support the Android best practices for scoped storage since Android 10 (API 29)

Android 11 introduces the MANAGE_EXTERNAL_STORAGE permission, which provides write access to files outside the app-specific directory and MediaStore. To learn more about this permission, and why most apps don't need to declare it to fulfill their use cases, see the guide on how to manage all files on a storage device.

If your app targets Android 11 (API level 30) or higher, the WRITE_EXTERNAL_STORAGE permission doesn't have any effect on your app's access to storage. This purpose-based storage model improves user privacy because apps are given access only to the areas of the device's file system that they actually use.

Scoped Storage: https://developer.android.com/training/data-storage#scoped-storage

Platform(s)

Android

Additional Context

https://developer.android.com/training/data-storage/manage-all-files https://developer.android.com/training/data-storage#permissions https://developer.android.com/training/data-storage#scoped-storage

imhoffd commented 3 years ago

There's a warning here that makes me think we should work on this later.

christianfl commented 3 years ago

Should this be mentioned here: https://capacitorjs.com/docs/updating/3-0#update-android-variables ?

I updated an app to Capacitor 3.0 and was wondering why saving and reading files from/to 'Documents' directory are not working anymore. That's not mentioned in the Filesystem docs either: https://capacitorjs.com/docs/apis/filesystem

There you can only see the warning for API 29, but not that the workaround is completely impossible with targeting API 30.

inexuscore commented 3 years ago

This is incredibly frustrating. I'm working on a new project using Ionic 6.x and Capacitor 3. Unfortunately, Capacitor doesn't have the essential plugins for typical workloads:

Just to name a few. I tried using existing Cordova plugins but they don't work either. I'm using an Android 11 device and apparently the media files cannot be accessed using the Capacitor Filesystem plugin or the cordova-file plugin.

I've been struggling for days, does anyone know how to make things work? Or do we need to wait for plugin updates?

lmsantanam commented 3 years ago

Hi, I've been working with the capacitor 3 file system and android 11 for some days now and after reading a lot, I believe that this feature request is the best place to address the problems getting access to read/write on certain folders of the device (documents or downloads for example). So just to give this issue a bump, I want to share a workaround I've came up with: image

For directory "External" the file (in my case a pdf that I receive from a http get call) saves to the folder: "Android > data > com.your.app.package > files ". And when the app is in Android 10 or below, the file will go to the documents folder (remember to have the permissions in the AndroidManifest.xml)

Is very ugly and I haven't tested with iOS yet, but is preferable for me than having my app trowing error messages every time I try to to execute the download function. Before this, with capacitor 2, everything worked both in Android and iOS just fine, documents folder was the place where my pdf files where being downloaded to

Another option I found interesting (but never tested) is this comment but messing with the build.gradle file is a different kind of poison than I'm willing to take

The other places I found interesting info:

https://github.com/ionic-team/capacitor/issues/4117

https://github.com/ionic-team/capacitor-plugins/issues/457

https://forum.ionicframework.com/t/file-storage-in-custom-directory/208868/4

I really hope some solution can be developed for the FileSystem API to give more control over how to download to an specific folder, I'm loving capacitor 3, but this issue is a big, big pain

roxteddy commented 3 years ago

BUMP

the problem on saying we should work on this later is that legacyStorage will be unavailable on september and I can't find any workaround. Saving to external is not a solution if you want to share your file with other apps. For example, saving a picture and opening it from social apps.

We really need a plugin using the Android MediaStore API !

Capacitor Community Media plugin doesn't help btw

roxteddy commented 3 years ago

Hello everybody !

I have made a little POC. It is an early alpha version so it may change a lot but feel free to have a look and tell me your opinion. https://github.com/agorapulse/capacitor-mediastore

Basically you use FileSystem plugin to write your file to Directory.Cache then you pass the return uri as path to my plugin and it will be saved to the photo gallery and available to other apps. You don't need WRITE_EXTERNAL_STORAGE permission and requestLegacyExternalStorage anymore.

It is tested on Android 10 and 12. Not sure on it rolls with older version yet.

roxteddy commented 3 years ago

This is incredibly frustrating. I'm working on a new project using Ionic 6.x and Capacitor 3. Unfortunately, Capacitor doesn't have the essential plugins for typical workloads:

  • Audio recorder
  • Video capture
  • File picker

Just to name a few. I tried using existing Cordova plugins but they don't work either. I'm using an Android 11 device and apparently the media files cannot be accessed using the Capacitor Filesystem plugin or the cordova-file plugin.

I've been struggling for days, does anyone know how to make things work? Or do we need to wait for plugin updates?

For file picking you can use the HTML tag <input>. Then you'll be able to browse the phone with a native picker.

roxteddy commented 3 years ago

@LuisManuel1983 what is your use case ? Do you want the user to be able to find the file from their Files app or something like that ?

I found this : https://developer.android.com/training/data-storage/shared/documents-files#create-file But the solution does not really match my Mediastore plugin. It is more something that should be implemented on FileSystem when we pick EXTERNAL_STORAGE or DOCUMENTS on a late version of Android. Basically, it will open a file picker to choose the destination. Since it is a user choice with native interface, we don't need any extra permission to achieve that.

I also found a class called MediaStore.Downloads. I could implement this in my plugin but it is 29+ only.

lmsantanam commented 3 years ago

@LuisManuel1983 what is your use case ? Do you want the user to be able to find the file from their Files app or something like that ?

I found this : https://developer.android.com/training/data-storage/shared/documents-files#create-file But the solution does not really match my Mediastore plugin. It is more something that should be implemented on FileSystem when we pick EXTERNAL_STORAGE or DOCUMENTS on a late version of Android. Basically, it will open a file picker to choose the destination. Since it is a user choice with native interface, we don't need any extra permission to achieve that.

I also found a class called MediaStore.Downloads. I could implement this in my plugin but it is 29+ only.

In my case is very simple and similar on how you would download a file using your browser, my app just opens some pdf files from a public website and there is an option to download them to the phone if the user wants to.... with Android 10 I was using the folder "Directory.Documents" but with android 11 that is not posible... so if the app is running on android 11 (or bigger) the pdf will download to "Directory.External" (external refers to the internal folder where the app is installed, see Directory options )

After downloading I send a message to the user, normally it just says "Saved to Documents" but in case of android 11+ I send a message with the complete route to the pdf file location... is ugly but pdf handling is such a secondary feature on my app that I don't have the time to develop and/or investigate anything more elaborated to make Android just download pdf's to the freaking documents folder and be done with it

roxteddy commented 2 years ago

Hello there !

I updated my plugin and made it available to npm : https://www.npmjs.com/package/@agorapulse/capacitor-mediastore

I think that is something that could be added to @capacitor-community/media but they haven't accepted any PR for months now. It's a shame for a community plugin. Maybe they should give the management to someone else if they lack time.

Anyway, I would be very happy to get some feedback.

For now it is Android only. You can save a picture or a video to library or any file to downloads folder. First, write your file to Directory.Cache, give the uri the plugin and then you can delete the cached file if you want.

lmsantanam commented 2 years ago

Hey, good to know, thanks! will probably test it next week, I just got feedback and I have to solve problems with iOS version, and one of those is downloading files

marcelpujol commented 2 years ago

Hi everyone! In my case, I have more or less the same scenario that @LuisManuel1983 has. Let me put some context here, my app (in Capacitor v3) needs to save a pdf or an image inside the mobile storage, so I am using the capacitor fileSystem plugin to write a file inside the Documents directory because I want to save each file in a Common path in order to be accessible from any application (for instance using a File Explorer).

So, now I am facing an internal issue in all the devices that are using Android 11 due to the internal changes (that android did) related to the file system. In fact, according to the documentation of the capacitor FileSystem plugin the Documents path is not working in Android versions 11 or above. Screenshot 2022-01-12 at 13 43 57

The error is the following one when I am trying to save a new file:
Screenshot 2022-01-12 at 13 41 17

As a workaround, I see that it is possible to add a new permission inside the AndroidManifest: and after that, if you go to the application permissions and inside the "Files and media" check the option: "Allow management of all files" everything works like a charm. MicrosoftTeams-image But to me this is not a valid solution for our users, because this is a manual bypass and it is ugly...

So, here I have several questions:

  1. Is it possible to write a file inside the Android's storage but in a common path like before? Because in my case, it is a must to be able to open this file using another external application, so it is not possible to use the External Directory because in that case these files are internal to the applications, and not typically visible to the user as media.
  2. Is it possible to select the "Allow management for all files" inside the modal that asks to the user the permissions? Because now, if you click allow, by default you are getting permissions but only to access to the media. After some research I see that it is possible to do that opening an intent (but of course, this is android native code: https://stackoverflow.com/questions/65293106/how-to-obtain-request-all-files-access-on-android-11), the plugin itself the one that has to offer you this option.

Thanks in advance!

FireStormHR commented 2 years ago

Another person here with the same problem. BUMP

We really need a (official) plugin using the Android MediaStore API. Without it, it is really hard to properly support Android 11 whenever you want to have a file publicly available in your system, which is quite often with apps. With this essential plugin missing (and problems with the share plugin, but thats another story), I would go as far as to currently discourage someone to use capacitor to develop for android 11. I think it is that high of a priority.

Has anyone found an Ionic article mentioning Android 11 storage? I would like to know as I failed to do so : )

For now I am also taking the route @LuisManuel1983 has taken, but I too think this a rather nasty solution, and the filesystem doesnt place the files at the expected mediaStore API locations. Another important difference in filesystem-plugin Directory.External behaviour in comparison with the mediaStore is that files will be deleted when the application is uninstalled.

For more info about scoped storage and android native code solutions, here are some other yet unmentioned links:

https://developer.android.com/about/versions/11/privacy/storage https://medium.com/androiddevelopers/android-11-storage-faq-78cefea52b7c https://developer.android.com/training/data-storage/shared/media#add-item

Hi, I've been working with the capacitor 3 file system and android 11 for some days now and after reading a lot, I believe that this feature request is the best place to address the problems getting access to read/write on certain folders of the device (documents or downloads for example). So just to give this issue a bump, I want to share a workaround I've came up with: image

For directory "External" the file (in my case a pdf that I receive from a http get call) saves to the folder: "Android > data > com.your.app.package > files ". And when the app is in Android 10 or below, the file will go to the documents folder (remember to have the permissions in the AndroidManifest.xml)

Is very ugly and I haven't tested with iOS yet, but is preferable for me than having my app trowing error messages every time I try to to execute the download function. Before this, with capacitor 2, everything worked both in Android and iOS just fine, documents folder was the place where my pdf files where being downloaded to

I really hope some solution can be developed for the FileSystem API to give more control over how to download to an specific folder, I'm loving capacitor 3, but this issue is a big, big pain

marcelpujol commented 2 years ago

I completely agree with you @FireStormHR, in my case I am doing a workaround that it is mixing the two approaches mentioned before. So, in my solution I am checking the device's android version and after that, if the android version is 10 or above it uses the @roxteddy's plugin, if not, it uses the Capacitor's filesystem ones.

Screenshot 2022-02-18 at 15 30 10 Screenshot 2022-02-18 at 15 31 57

It seems that it is working perfectly: For android 10 or above -> all the files are stored in the download folder using the @roxteddy's plugin For android < 10 -> all the files are stored in the Documents Directory using the Capacitor's filesystem plugin

The only thing that it is missing, it is to be able to delete files using the @roxteddy's plugin, because now the problem, that I am facing is that it is accumulating all these files inside the Downloads folder, so it would be useful to have a function that allows you to delete a specific file passing the its concrete path.

What do you think?? Thanks!

chidav77 commented 2 years ago

I completely agree with you @FireStormHR, in my case I am doing a workaround that it is mixing the two approaches mentioned before. So, in my solution I am checking the device's android version and after that, if the android version is 10 or above it uses the @roxteddy's plugin, if not, it uses the Capacitor's filesystem ones.

Screenshot 2022-02-18 at 15 30 10 Screenshot 2022-02-18 at 15 31 57

It seems that it is working perfectly: For android 10 or above -> all the files are stored in the download folder using the @roxteddy's plugin For android < 10 -> all the files are stored in the Documents Directory using the Capacitor's filesystem plugin

The only thing that it is missing, it is to be able to delete files using the @roxteddy's plugin, because now the problem, that I am facing is that it is accumulating all these files inside the Downloads folder, so it would be useful to have a function that allows you to delete a specific file passing the its concrete path.

What do you think?? Thanks!

Hi, sorry Im not an expert. Im trying to save a file (in my case an mp3 file) in a folder that an be accessible from a connected pc or a file Explorer app. I looked at your code, but I really dont understand how to valorize the path and filename variable. Does someone has a full example?

The mediastore plugin is usable on Android <10? Thanks!

jake-newsom commented 2 years ago

Chiming in to add a use case and to say that we really need this officially supported somehow.

In our app, we provide a few different types of media but most importantly right now is video files (which are originally hosted on a remote server). We also are trying to support sharing these video files to social medias.

At first we set it up so that if you try to share the video file, it first downloads to our app's cache folder and then shares that local file. The problem we started running into is Instagram has done an absolutely awful job keeping their own app's sharing UIs updated and consistent to the point that user's think our app is broken on iOS when they attempt to share a video file.

Our solution here was to build a custom share menu that skips Instagram's native share capabilities and opens their app up to the create post page. This should automatically show the most recent item in their gallery, however this means the video file now needs to come out of our app's cache folder and into the public gallery folder on the device.

LilyOfTheWest commented 2 years ago

Hello everyone ! Just want to say many many thanks for your proposition @marcelpujol We were blocked for like a week without a proper solution to tackle the Android changes to the storage starting with Android 10. I implemented your solution and it works wonderfully well on Android 9, 10 and 11 ! Also thank you @roxteddy for your plugin, it's amazing ;) ! Completely agree with all of you though, we should have the official plugin for MediaStore.

itsTeknas commented 2 years ago

My App got rejected because of this, I'm downloading a file from an API and storing it locally on the device.

They're suggesting to use the MANAGE_EXTERNAL_STORAGE permission instead.

Screenshot 2022-07-29 at 11 15 03 AM

The plugin needs an option to use the Media Store API.

nonsenseless commented 2 years ago

@itsTeknas What triggered your app rejection? Were you using @roxteddy's plugin?

FranciskoNeves commented 10 months ago

I completely agree with you @FireStormHR, in my case I am doing a workaround that it is mixing the two approaches mentioned before. So, in my solution I am checking the device's android version and after that, if the android version is 10 or above it uses the @roxteddy's plugin, if not, it uses the Capacitor's filesystem ones. Screenshot 2022-02-18 at 15 30 10 Screenshot 2022-02-18 at 15 31 57 It seems that it is working perfectly: For android 10 or above -> all the files are stored in the download folder using the @roxteddy's plugin For android < 10 -> all the files are stored in the Documents Directory using the Capacitor's filesystem plugin The only thing that it is missing, it is to be able to delete files using the @roxteddy's plugin, because now the problem, that I am facing is that it is accumulating all these files inside the Downloads folder, so it would be useful to have a function that allows you to delete a specific file passing the its concrete path. What do you think?? Thanks!

Hi, sorry Im not an expert. Im trying to save a file (in my case an mp3 file) in a folder that an be accessible from a connected pc or a file Explorer app. I looked at your code, but I really dont understand how to valorize the path and filename variable. Does someone has a full example?

The mediastore plugin is usable on Android <10? Thanks!

No it isn't, at least with this plugin (https://github.com/agorapulse/capacitor-mediastore) it gave me an error.