apache / cordova-plugin-file

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

Storage updates in Android 11 #426

Closed HarelM closed 2 years ago

HarelM commented 4 years ago

Feature Request

Support the android 11 new file system based policy.

Motivation Behind Feature

Support the changes that are planned for android 11 (API level 30)

Feature Description

I'm not sure how to fully describe this, but basically support what's documented here: https://developer.android.com/about/versions/11/privacy/storage

Alternatives or Workarounds

Not targeting API level 30 until this is supported?

jfoclpf commented 3 years ago

Or is there a way to use this plugin on the 'Files' android app so the user can save the file on his device ?

I recommend my users to simply install this simple and light add-on, I'd say it's not even an app, it simply allows you to share to file system. But any file system app allows you to do that.

victorvhpg commented 3 years ago

@jfoclpf Will this work on Android 11 sdk 30+ ?

jfoclpf commented 3 years ago

@jfoclpf Will this work on Android 11 sdk 30+ ?

Yes, so far the feedback from the users is that it works

joshuaokpako commented 3 years ago

I am using ionic 4 and I faced the same issue so I tried the ionic 4 version of resolveDirectoryUrl(cordova.file.externalRootDirectory, function (dirEntry) {)) and on success function I used dirEntry.getDirectory ('Download'... and I can create folders and save files to the Download folders on android 11 without adding android:requestLegacyExternalStorage="true". Here is the code for ionic 4. I have also tried to convert it to the normal web version.

createDirectory(rootDir) {
    return new Promise((res, rej) => {
      this.file.resolveDirectoryUrl(rootDir).then((rootDirEntry)=>{

        rootDirEntry.getDirectory('Download', { create: false, exclusive:false},  (dirEntry) =>{
          dirEntry.getDirectory('updates', { create: true, exclusive:false}, (subDirEntry) =>{
            this.updateDir = subDirEntry;
            this.appDir = dirEntry;
            res(this.updateDir)
          }, (err)=> {
            this.onErrorGetDir(rootDir+'Download/','updates');
            rej(err)
          })
        }, (err)=> {
          this.onErrorGetDir(rootDir,'Download')
          rej(err)
        })
      })

    })
  }

and the normal web version. Note that I have only tested the ionic 4 version of this code.

function createDirectory(rootDir /*cordova.file.externalRootDirectory*/) {
    window.resolveDirectoryURL(rootDir, function (rootDirEntry) {
      dirEntry.getDirectory('Download', { create: false, exclusive:false}, function (dirEntry) {
        dirEntry.getDirectory('updates', { create: true, exclusive:false}, function (subDirEntry) {
            /* delete a file or donwload a file using cordova-plugin-advanced-http.
                I tested mine using cordova-plugin-advanced-http to download a file to this folder */
            createFile(subDirEntry, "fileInNewSubDir.txt");

        }, onErrorGetDir);
      }, onErrorGetDir);
    })
  }

This doesn't allow you to write or read from the root directory but works with the download directory and probably other sub directories in the root directory. You will need READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions.

jfoclpf commented 3 years ago

@joshuaokpako that's strange, normally you shouldn't be able to write to cordova.file.externalRootDirectory + 'Download/' without the android:requestLegacyExternalStorage to true (check the answer from @breautek ) after API 29. Are you using the dev version of this file plugin (i.e. fetched from github)?

joshuaokpako commented 3 years ago

@jfoclpf I am using ionic 4 and the ionic cordova plugin is built on this file plugin. I have attached the link to the ionic 4 website that references this plugin https://ionicframework.com/docs/native/file

jfoclpf commented 3 years ago

@joshuaokpako ah, now I see, you are using the npm version (npm install cordova-plugin-file) and not the dev version and in the npm version android:requestLegacyExternalStorage is not set to true. Thus it works (in theory just up to Android 10).

victorvhpg commented 3 years ago

I am using ionic 4 and I faced the same issue so I tried the ionic 4 version of resolveDirectoryUrl(cordova.file.externalRootDirectory, function (dirEntry) {)) and on success function I used dirEntry.getDirectory ('Download'... and I can create folders and save files to the Download folders on android 11 without adding android:requestLegacyExternalStorage="true". Here is the code for ionic 4. I have also tried to convert it to the normal web version.

createDirectory(rootDir) {
    return new Promise((res, rej) => {
      this.file.resolveDirectoryUrl(rootDir).then((rootDirEntry)=>{

        rootDirEntry.getDirectory('Download', { create: false, exclusive:false},  (dirEntry) =>{
          dirEntry.getDirectory('updates', { create: true, exclusive:false}, (subDirEntry) =>{
            this.updateDir = subDirEntry;
            this.appDir = dirEntry;
            res(this.updateDir)
          }, (err)=> {
            this.onErrorGetDir(rootDir+'Download/','updates');
            rej(err)
          })
        }, (err)=> {
          this.onErrorGetDir(rootDir,'Download')
          rej(err)
        })
      })

    })
  }

and the normal web version. Note that I have only tested the ionic 4 version of this code.

function createDirectory(rootDir /*cordova.file.externalRootDirectory*/) {
    window.resolveDirectoryURL(rootDir, function (rootDirEntry) {
      dirEntry.getDirectory('Download', { create: false, exclusive:false}, function (dirEntry) {
        dirEntry.getDirectory('updates', { create: true, exclusive:false}, function (subDirEntry) {
            /* delete a file or donwload a file using cordova-plugin-advanced-http.
                I tested mine using cordova-plugin-advanced-http to download a file to this folder */
            createFile(subDirEntry, "fileInNewSubDir.txt");

        }, onErrorGetDir);
      }, onErrorGetDir);
    })
  }

This doesn't allow you to write or read from the root directory but works with the download directory and probably other sub directories in the root directory. You will need READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions.

This only work for CREATE files. i can't delete/update a file inside 'Download' folder without flag 'requestLegacyExternalStorage'
Android 11 sdk 30+

joshuaokpako commented 3 years ago

@victorvhpg I believe the new android 11 update will only allow you delete files that are created by your app in the Download folder. I haven't tested deleting any other file but I have used the code on ionic 4 to delete files I created in the Download folder.

delelteFile(dir:DirectoryEntry,name) {
    return new Promise((res, rej) => {
      dir.getFile(name, {create: false}, function (fileEntry) {
          fileEntry.remove( () =>{
            res("deleted")
          }, function (error) {
            rej(error)
          });
      }, function () {
        res("ok")
      });
    })

  }
joshuaokpako commented 3 years ago

I believe this is the reason why the code above will work on android 11 https://developer.android.com/about/versions/11/privacy/storage#media-direct-file-native

breautek commented 3 years ago

I would like to invite people to try out my fork and give it a test: https://github.com/breautek/cordova-plugin-file/tree/feat/api30

My fork removes the requestLegacyExternalFlag which means it should work on both API 29 and API 30+. It ensures that it prompts for the proper permission in read/write external storage.

Here are some highlight changes:

interstellerS commented 3 years ago

@breautek thanks for the proposed solution.

Does this also fix Writing to external Files like ( /Download ) on SDK 30 ?

breautek commented 3 years ago

@breautek thanks for the proposed solution.

Does this also fix Writing to external Files like ( /Download ) on SDK 30 ?

It should. I didn't test writing to the Downloads directory explicitly, but feel free to augment my test app https://github.com/breautek/cordova-file-api30-test-app/blob/86b1c9135f1e68e62ddc2d874fd82cd933cb9392/www/js/index.js#L173

breautek commented 3 years ago

@breautek thanks for the proposed solution. Does this also fix Writing to external Files like ( /Download ) on SDK 30 ?

It should. I didn't test writing to the Downloads directory explicitly, but feel free to augment my test app https://github.com/breautek/cordova-file-api30-test-app/blob/86b1c9135f1e68e62ddc2d874fd82cd933cb9392/www/js/index.js#L173

thanks for the proposed solution.

My fork is not intended to be a solution, I just want to make sure that my changes are covering API 30 properly in wide range of use cases before I prepare a PR.

interstellerS commented 3 years ago

@breautek In your test you write to file to externalApplicationStorageDirectory , I tested it . That works fine.

I intend to save the file to Download folder inside externalRootDirectory.

externalRootDirectory is readable, but not writable. You cannot create directories or files inside this directory anymore.

I read your previous comment , but Is there any workaround or other plugin/method to save files to the public android Download directory ?

jfoclpf commented 3 years ago

@interstellerS I know the thread is long, but we have already discussed that previously.

The current workaround is to use the npm version of this plugin and not the dev version, just run to be sure you're using the npm version

cordova plugin rm cordova-plugin-file
cordova plugin add cordova-plugin-file

The npm version does not use requestLegacyExternalFlag on which Google complains, on and after Android 11.

Then install the plugin cordova-plugin-x-socialsharing and use the options.files Array property. This let the users save the file wherever they want, instead of you trying to find the correct path. If you want to be sure your users are able to share to the file system, you can suggest them to install this simple APP, which is indeed just an addon (it will not create any new icon and it's not openable, it simply allows users to share a file to the file system).

sudeekshachip1003 commented 3 years ago

I would like to invite people to try out my fork and give it a test: https://github.com/breautek/cordova-plugin-file/tree/feat/api30

My fork removes the requestLegacyExternalFlag which means it should work on both API 29 and API 30+. It ensures that it prompts for the proper permission in read/write external storage.

Here are some highlight changes:

  • It's possible that you've already granted or denied external storage permissions, in which case it may just work or it may not work. If you're getting a file not found kind of errors, or file listings are simply empty when you expect there to be content, that probably means you're lacking permissions. Android does not tell us that a file exists but you lack permissions to view; it just gives a file not found error. I assume this is for privacy reasons.
  • You can use adb shell pm reset-permissions to reset your permissions on all apps back to a state of "not requested", so that you can test as if the user is running your app for the first time.
  • externalRootDirectory is readable, but not writable. You cannot create directories or files inside this directory anymore.
  • I used https://github.com/breautek/cordova-file-api30-test-app as a test app. If something is not working right for you on my fork, feel free to make a pull request and add a test case.
  • I've seen Google is cracking down, requiring external use via READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE; in order to use these permissions (and this plugin for accessing external storage) you will need to give justification. You'll going to have to evaluate if your app needs external storage access, and if not, then start migrating to internal storage. As a result of this, I'm planning on making this plugin add the external storage permissions as a configurable option.
  • This plugin is currently not compatible with cordova-android@10/nightly. So please only test using cordova-android@9.1

Im having a use case of reading the files from shared storage which is failing in android 11 currently. I had tried your solution. It asks for the permissions required and requestLegacyExternalFlag is removed. But still unable to read the files.

As the new apps would need to target SDK 30, any workaround for this issue? Any way to read files from shared storage(Downloads/images..).

Thanks

aphyl30 commented 3 years ago

Hello, I confirm that it works with sdk 29.0.3. I am writing an application that saves files that come either from a web server (downloaded) or created by my application. I'm using sdk 30 and it works for downloaded files with "cordova-plugin-file-transfer" but not locally with "cordova-plugin-file" Can someone explain me the difference between the two? Thanks

angeltodorovyara commented 3 years ago

I store a created file inside files folder in the internal storage and i want the user to be able to send it via email using cordova-plugin-x-socialshearing. But when the Gmail app or Google Drive opens it says that the file couldn't be attached. I don't understand where the problem is, where this file must be stored...

HarelM commented 3 years ago

Check the file size. File larger than 25 mb won't be attached. I have no issues with the plugin and gmail, I use it for reporting an issue in my app to attach log files.

atebsy commented 3 years ago

Chooser

@Vatsov file chooser (this.chooser.getFile()) also returns base64 data, you can convert that into blob, then use the this.file.writeFile() method to save the file into your app directory

JoshuaEGonzalezRuiz commented 3 years ago

Hello, I was facing a similar problem, when I needed to read a file from the storage of an Android device, the plugin returns null in the contents of the file.

The reason for this is the "partial" permission implemented in Android 11, although the user gives the storage permission this always stays in "only multimedia content".

So what I did was send the user to the settings section of the app and tell them to change the storage permission to "allow access to all files". And with that change I was already able to read the files in any directory on the device on Android 11.

Note: You need to implement this in your config.xml for the storage permission to work well on Android 11 <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

I hope and this will help you.

jfoclpf commented 3 years ago

Tell the user to allow the app to access all files is not a good practice, it poses potential security problems and it is exactly that which Google was trying to avoid with the new policies on Android 11.

A sábado, 5/06/2021, 16:41, Joshua Eduardo González Ruíz < @.***> escreveu:

Hello, I was facing a similar problem, when I needed to read a file from the storage of an Android device, the plugin returns null in the contents of the file.

The reason for this is the "partial" permission implemented in Android 11, although the user gives the storage permission this always stays in "only multimedia content".

So what I did was send the user to the settings section of the app and tell them to change the storage permission to "allow access to all files". And with that change I was already able to read the files in any directory on the device on Android 11.

Note: You need to implement this in your config.xml for the storage permission to work well on Android 11

I hope and this will help you.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/apache/cordova-plugin-file/issues/426#issuecomment-855249720, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA6M4DLMZZOLJK7STKAVVOLTRIZR7ANCNFSM4SAAWWGQ .

JoshuaEGonzalezRuiz commented 3 years ago

Tell the user to allow the app to access all files is not a good practice, it poses potential security problems and it is exactly that which Google was trying to avoid with the new policies on Android 11. A sábado, 5/06/2021, 16:41, Joshua Eduardo González Ruíz < @.***> escreveu:

I know, but that was the only way I could get the process going. If I find any other way, I will gladly share it around here until there is an official form.

bondulich commented 3 years ago

Hello, i found plugin for save image base64 in public directory, using api Media Store. Work in android 10 and 11. I hope and this will help someone.

https://github.com/Heartade/cordova-plugin-android-mediastore

drogerie21 commented 3 years ago

Hello, I was facing a similar problem, when I needed to read a file from the storage of an Android device, the plugin returns null in the contents of the file.

The reason for this is the "partial" permission implemented in Android 11, although the user gives the storage permission this always stays in "only multimedia content".

So what I did was send the user to the settings section of the app and tell them to change the storage permission to "allow access to all files". And with that change I was already able to read the files in any directory on the device on Android 11.

Note: You need to implement this in your config.xml for the storage permission to work well on Android 11 <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

I hope and this will help you.

Unfortunately this is not working for me! I try to write a file from a html input tag. This is working with eg files from the Downloads-Folder. But it is not working with files from google drive. Setting the permission to access all files ("android.permission.MANAGE_EXTERNAL_STORAGE") does not change this behaviour. I get this error message: DOMException: The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.

victorvhpg commented 3 years ago

Hello, i found plugin for save image base64 in public directory, using api Media Store. Work in android 10 and 11. I hope and this will help someone.

https://github.com/Heartade/cordova-plugin-android-mediastore

@HarelM @breautek Maybe the solution is mediaStore

HarelM commented 3 years ago

For android 10 and 11 that's probably true, but if you need to support 9 in one plugin it's hard...

Luro91 commented 3 years ago
  • This plugin is currently not compatible with cordova-android@10/nightly. So please only test using cordova-android@9.1

Is it compatible with cordova-android@10 now? I tested it and it works for me so far.

jfoclpf commented 3 years ago

@breautek please highlight your comment about the test fork you created, because people are not reading it since the thread is becoming too long.

Newcomers: test the following fork because it was made to overcome all the problems related with storage on Android 11 and beyond

cordova plugin add https://github.com/breautek/cordova-plugin-file/tree/feat/api30

and downgrade to cordova 9.1 if necessary (latest is 10)

npm i -g cordova-android@9.1

More info here: https://github.com/apache/cordova-plugin-file/issues/426#issuecomment-830841544

victorvhpg commented 3 years ago

@breautek any update?

sksk008 commented 3 years ago

@breautek please highlight your comment about the test fork you created, because people are not reading it since the thread is becoming too long.

Newcomers: test the following fork because it was made to overcome all the problems related with storage on Android 11 and beyond

cordova plugin add https://github.com/breautek/cordova-plugin-file/tree/feat/api30

and downgrade to cordova 9.1 if necessary (latest is 10)

npm i -g cordova-android@9.1

More info here: #426 (comment)

but google not allowed android api 29 anymore we must have to update api level 30 which is only possible by cordova 10.0.0 so we must solved this issue to upload new apk in playstore.

skmbr commented 3 years ago

I've read through this entire thread (trying to follow as much as I can), the Google docs on scoped storage, and other issues and stackoverflow posts and I'm still not 100% sure that this issue applies to me, so if anyone can tell me I'd really appreciate it...

For a feature I'm working on (involving opening a video, overlaying text on it with canvas, and capturing the output) I need the video and page to be same origin. So I've tried to use API30 and cordova-android@10.0.1 for the WebViewAssetLoader functionality to serve my app from https://localhost.

The problem I have is that after updating (from API29 / cordova-android@9.1), when I select a video from the photo library, using previously working code like this:

          navigator.camera.getPicture(
            file => {
              file = file.indexOf('file://') === 0 ? file : 'file://' + file
              window.resolveLocalFileSystemURL(file, (fileEntry) => {
                this.video = fileEntry.toInternalURL() // Vue.js data property that populates video src
              })
            },

            () => {},

            {
              destinationType: Camera.DestinationType.FILE_URI,
              sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
              mediaType: Camera.MediaType.VIDEO
            }
          )
        }

... I get an error like this in the console:

GET cdvfile://localhost/sdcard/DCIM/Camera/VID_20210802_200106.mp4 net::ERR_UNKNOWN_URL_SCHEME

If I go back to API29 / cordova-android@9.1 it works fine again.

Where I'm not sure if this is the same issue is from comments like this one by @HarelM https://github.com/apache/cordova-plugin-file/issues/426#issuecomment-751977160 :

@Vatsov Are you reading a file a user selects? If so I don't think there's an issue that is related to Android 11, or at least I hope not as you open the file picker and this should be OK even in Android 11 as far as I know.

...and things that I read in google's docs that seem to suggest that reading the users photo/video library is treated differently to accessing other types of file.

Regardless, I've still tried adding the MANAGE_EXTERNAL_STORAGE permission, but that hasn't seemed to have any effect.

So can anyone tell me if this is the same issue? Or is it a different one caused by the upgrade to API30 and/or cordova-android@10.0.1 ?

To be honest I'm feeling a little out of my depth here and would very much like to get back to my cosy javascript safe place! :)

Thanks in advance for any help or suggestions!

sytolk commented 3 years ago

@skmbr on my side the latest working versions of android and camera plugins are: "cordova-android": "^9.1.0", "cordova-plugin-camera": "^5.0.2",

I dont think that your issue is related with this. Its look like native Java code error from incompatible versions of cordova-android and cordova-plugin-camera. Try to check Java stacktrace in Android Studio logging.

skmbr commented 3 years ago

@skmbr on my side the latest working versions of android and camera plugins are: "cordova-android": "^9.1.0", "cordova-plugin-camera": "^5.0.2",

I dont think that your issue is related with this. Its look like native Java code error from incompatible versions of cordova-android and cordova-plugin-camera. Try to check Java stacktrace in Android Studio logging.

Thanks @sytolk

The console error I posted is from the javascript console in chrome devtools after building my app in debug mode.

I assumed it wasn't anything to do with the camera plugin as that's returning the correct internal file path. I thought cordova-plugin-file was responsible for resolveLocalFileSystemURL and cdvfile:// links?

Will I need to wait for cordova-android@10 support in cordova-plugin-camera ?

sksk008 commented 3 years ago

I've read through this entire thread (trying to follow as much as I can), the Google docs on scoped storage, and other issues and stackoverflow posts and I'm still not 100% sure that this issue applies to me, so if anyone can tell me I'd really appreciate it...

For a feature I'm working on (involving opening a video, overlaying text on it with canvas, and capturing the output) I need the video and page to be same origin. So I've tried to use API30 and cordova-android@10.0.1 for the WebViewAssetLoader functionality to serve my app from https://localhost.

The problem I have is that after updating (from API29 / cordova-android@9.1), when I select a video from the photo library, using previously working code like this:

          navigator.camera.getPicture(
            file => {
              file = file.indexOf('file://') === 0 ? file : 'file://' + file
              window.resolveLocalFileSystemURL(file, (fileEntry) => {
                this.video = fileEntry.toInternalURL() // Vue.js data property that populates video src
              })
            },

            () => {},

            {
              destinationType: Camera.DestinationType.FILE_URI,
              sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
              mediaType: Camera.MediaType.VIDEO
            }
          )
        }

... I get an error like this in the console:

GET cdvfile://localhost/sdcard/DCIM/Camera/VID_20210802_200106.mp4 net::ERR_UNKNOWN_URL_SCHEME

If I go back to API29 / cordova-android@9.1 it works fine again.

Where I'm not sure if this is the same issue is from comments like this one by @HarelM #426 (comment) :

@Vatsov Are you reading a file a user selects? If so I don't think there's an issue that is related to Android 11, or at least I hope not as you open the file picker and this should be OK even in Android 11 as far as I know.

...and things that I read in google's docs that seem to suggest that reading the users photo/video library is treated differently to accessing other types of file.

Regardless, I've still tried adding the MANAGE_EXTERNAL_STORAGE permission, but that hasn't seemed to have any effect.

So can anyone tell me if this is the same issue? Or is it a different one caused by the upgrade to API30 and/or cordova-android@10.0.1 ?

To be honest I'm feeling a little out of my depth here and would very much like to get back to my cosy javascript safe place! :)

Thanks in advance for any help or suggestions! I dont try this but i think you can solve your issue using this as suggest here <preference name="AndroidInsecureFileModeEnabled" value="true" />

skmbr commented 3 years ago

@krunalsk007

I dont try this but i think you can solve your issue using this as suggest here <preference name="AndroidInsecureFileModeEnabled" value="true" />

No, that puts everything back to using file://

I need https://localhost and WebViewAssetLoader to solve my origin issues.

anshumanfs commented 3 years ago

Actually i have the same problem while i upgraded to API 30. I got error code 9 also. As google upgraded its android protocol set for storage we have to use "scoped storage".

  1. In my application i have to capture image, then i have to compress using Javascript image compressors (usually gives better quality than the "cordova-plugin-camera" compressors and have more flexibility) and have to write compressed files using "cordova-plugin-file" to storage.

  2. Previously i am using "LocalFileSystem.PERSISTENT" and with API 30 i couldn't access that region of my file system due to restrictions imposed.

Example:- window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fs) { console.log('file system open: ' + fs.name);fs.root.getFile("newPersistentFile.txt", { create: true, exclusive: false }, function (fileEntry) {console.log("fileEntry is file?" + fileEntry.isFile.toString()); // fileEntry.name == 'someFile.txt' // fileEntry.fullPath == '/someFile.txt' writeFile(fileEntry, null);}, onErrorCreateFile); }, onErrorLoadFs);

  1. Then i changed "LocalFileSystem.PERSISTENT" with "cordova.file.dataDirectory" and it worked. Its also logically correct as per the restrictions imposed now a days.

Example :-

window.resolveLocalFileSystemURL(cordova.file.dataDirectory, function (dirEntry) { console.log('file system open: ' + dirEntry.name); var isAppend = true; createFile(dirEntry, "fileToAppend.txt", isAppend); }, onErrorLoadFs);

This example can also be used to create new files, if you provide unique file name each time. Like below.

Example :-

window.resolveLocalFileSystemURL(cordova.file.dataDirectory, function(fs) { createFile(fs, outputfilename); function createFile(dirEntry, fileName, isAppend) { // Creates a new file or returns the file if it already exists (use unique file name each time) . dirEntry.getFile(fileName, { create: true, exclusive: false }, function(fileEntry) {writeFile(fileEntry, result); }, function(err) { console.log(err); });}function writeFile(fileEntry, dataObj) { // Create a FileWriter object for our FileEntry (log.txt). fileEntry.createWriter(function(fileWriter) {fileWriter.onwriteend = function() {console.log("File written successfully...");console.log(fileEntry); };fileWriter.onerror = function(e) { console.log("Failed to write file: " + e.toString()); };// If data object is not passed in, // create a new Blob instead. if (!dataObj) { dataObj = new Blob(['some file data'], { type: 'text/plain' }); }fileWriter.write(dataObj); }); } }, function(error) { console.log(error); });

Correct me if something i missed. Thank you all.

anshumanfs commented 3 years ago

I've read through this entire thread (trying to follow as much as I can), the Google docs on scoped storage, and other issues and stackoverflow posts and I'm still not 100% sure that this issue applies to me, so if anyone can tell me I'd really appreciate it... For a feature I'm working on (involving opening a video, overlaying text on it with canvas, and capturing the output) I need the video and page to be same origin. So I've tried to use API30 and cordova-android@10.0.1 for the WebViewAssetLoader functionality to serve my app from https://localhost. The problem I have is that after updating (from API29 / cordova-android@9.1), when I select a video from the photo library, using previously working code like this:

          navigator.camera.getPicture(
            file => {
              file = file.indexOf('file://') === 0 ? file : 'file://' + file
              window.resolveLocalFileSystemURL(file, (fileEntry) => {
                this.video = fileEntry.toInternalURL() // Vue.js data property that populates video src
              })
            },

            () => {},

            {
              destinationType: Camera.DestinationType.FILE_URI,
              sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
              mediaType: Camera.MediaType.VIDEO
            }
          )
        }

... I get an error like this in the console: GET cdvfile://localhost/sdcard/DCIM/Camera/VID_20210802_200106.mp4 net::ERR_UNKNOWN_URL_SCHEME If I go back to API29 / cordova-android@9.1 it works fine again. Where I'm not sure if this is the same issue is from comments like this one by @HarelM #426 (comment) :

@Vatsov Are you reading a file a user selects? If so I don't think there's an issue that is related to Android 11, or at least I hope not as you open the file picker and this should be OK even in Android 11 as far as I know.

...and things that I read in google's docs that seem to suggest that reading the users photo/video library is treated differently to accessing other types of file. Regardless, I've still tried adding the MANAGE_EXTERNAL_STORAGE permission, but that hasn't seemed to have any effect. So can anyone tell me if this is the same issue? Or is it a different one caused by the upgrade to API30 and/or cordova-android@10.0.1 ? To be honest I'm feeling a little out of my depth here and would very much like to get back to my cosy javascript safe place! :) Thanks in advance for any help or suggestions! I dont try this but i think you can solve your issue using this as suggest here <preference name="AndroidInsecureFileModeEnabled" value="true" />

Is your Application has File permissions enabled ? Check Android Manifest for these three

`

`

if not add these in your AndroidManifest.xml . Try to build with these. if problem exists reply me under same thread.

rmeyers4 commented 3 years ago

I work on an application that allows users to create directories in externalRootDirectory and save various files into these directories (QR codes, log files, csv files, etc). Users expect to be able to copy files off of the tablet when using a Windows computer to explore the tablet file system.

After spending a bunch of time trying to migrate this functionality to allow files to be saved to externalApplicationStorageDirectory, I discovered a potentially much simpler solution. From my testing so far, applications using this plugin and opting in to Scoped Storage can still create directories and write files to the Documents folder in externalRootDirectory.

I tested this on API 29 with Scoped Storage enabled on an Android 11 tablet by going to Developer Options -> App Compatibility Changes -> and turning on FORCE_ENABLED_SCOPED_STORAGE and all of the "Enabled for targetSdkVersion > 29 features" except for NATIVE_HEAP_POINTER_TAGGING. I have no idea why this flag prevents files from being created, but luckily it is not (currently) required for apps targeting API 30.

I then targeted API 30 and added the following to config.xml:

  <edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application">
    <application android:allowNativeHeapPointerTagging="false"/>
  </edit-config>

https://developer.android.com/guide/topics/manifest/application-element#allowNativeHeapPointerTagging

My application seems to function as it did without scoped storage as long as I prefix my paths with Documents/ when targeting API 29 and API 30. We will need to train our users to expect files to be in this location, but (so far) this seems much simpler than trying to use app-specific storage.

I hope this helps someone else, or hope someone tells me why I'm wrong!

rmeyers4 commented 3 years ago

@anshuman61 : MANAGE_EXTERNAL_STORAGE is not a long-term solution. Once the Play Store requires applications to target API 30 later this year, Google is also cracking down on this permission and will not allow you to upload apps to the Play Store that use this permission unless the central purpose is file-system manipulation (a backup app, for example). Even in that case, you have to submit an application to attain this permission including a demonstration of why your app requires MANAGE_EXTERNAL_STORAGE.

See https://support.google.com/googleplay/android-developer/answer/10467955?hl=en#zippy=%2Cpermitted-uses-of-the-all-files-access-permission

chrisjdev commented 3 years ago

This is quite a thread. I was looking at it trying to figure out why I couldn't read a file after the user selects it from a system file picker. My issue is specifically for targeting API level 30, and my application was attempting to resolve a given URL to a path on the phone, then use a FileInputStream to read the file and write it into a my applications data. The solution to this problem is to call something like context.getContentResolver().openInputStream(uri) to read the file instead. I'm still working out how to connect everything, because right now cordova-android@10, cordova-plugin-filechooser, cordova-plugin-filepath, and cordova-plugin-file are all in use. Just putting this out there, even though it won't help anyone trying to figure out why they can't write files with scoped storage.

konnectappdev commented 3 years ago

Hello, i found plugin for save image base64 in public directory, using api Media Store. Work in android 10 and 11. I hope and this will help someone.

https://github.com/Heartade/cordova-plugin-android-mediastore

Thanks! This seems to work for us. In our ionic cordova app we want users to be able to download photos and videos stored in our protected app space to their media directory.

We have extended the repo downloading video files:

https://github.com/konnectappdev/cordova-plugin-android-mediastore

darnok333 commented 3 years ago

Hello, I just want to share a solution I found to my problem as it's related to this thread.

Problem with SDK 30 : Our team used the path externalApplicationStorageDirectory to store some assets of the application, wich are downloaded the first time the user connect to the application. But we weren't able to access those files, and got an ACCESS_DENIED error.

Solution : I made a little modification in CordovaActivity.java in init() function

protected void init() {
    appView = makeWebView();
    createViews();
    if (!appView.isInitialized()) {
        appView.init(cordovaInterface, pluginEntries, preferences);
    }

    /********** MODIFICATION SDK 30 **********/
        WebView webView = (SystemWebView)(appView.getEngine().getView());
        WebSettings webSettings = webView.getSettings();
        webSettings.setAllowFileAccess(true);
    /**********************************/

    cordovaInterface.onCordovaInit(appView.getPluginManager());

    // Wire the hardware volume controls to control media if desired.
    String volumePref = preferences.getString("DefaultVolumeStream", "");
    if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
        setVolumeControlStream(AudioManager.STREAM_MUSIC);
    }
 }

you also need to add these import :

import android.webkit.WebView;
import android.webkit.WebSettings;
import org.apache.cordova.engine.SystemWebView;

Hope this can help some of you :)

erisu commented 3 years ago

Hello, I just want to share a solution I found to my problem as it's related to this thread.

Problem with SDK 30 : Out team used the path externalApplicationStorageDirectory to store some assets of the application, wich are downloaded the first time the user connect to the application. But we weren't able to access those files, and got an ACCESS_DENIED error.

Solution : I made a little modification in CordovaActivity.java in init() function

protected void init() {
    appView = makeWebView();
    createViews();
    if (!appView.isInitialized()) {
        appView.init(cordovaInterface, pluginEntries, preferences);
    }

    /********** MODIFICATION SDK 30 **********/
        WebView webView = (SystemWebView)(appView.getEngine().getView());
        WebSettings webSettings = webView.getSettings();
        webSettings.setAllowFileAccess(true);
    /**********************************/

    cordovaInterface.onCordovaInit(appView.getPluginManager());

    // Wire the hardware volume controls to control media if desired.
    String volumePref = preferences.getString("DefaultVolumeStream", "");
    if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
        setVolumeControlStream(AudioManager.STREAM_MUSIC);
    }
 }

you also need to add these import :

import android.webkit.WebView;
import android.webkit.WebSettings;
import org.apache.cordova.engine.SystemWebView;

Hope this can help some of you :)

I also want to include what Google says about setting setAllowFileAccess to true, for informational purposes:

setAllowFileAccess

Enables or disables file access within WebView. Note that this enables or disables file system access only. Assets and resources are still accessible using file:///android_asset and file:///android_res.

Note: Apps should not open file:// URLs from any external source in WebView, don't enable this if your app accepts arbitrary URLs from external sources. It's recommended to always use androidx.webkit.WebViewAssetLoader to access files including assets and resources over http(s):// schemes, instead of file:// URLs. To prevent possible security issues targeting Build.VERSION_CODES.Q and earlier, you should explicitly set this value to false.

israeltrejo commented 3 years ago

I changed the android version in which the application is focused adding this tag in the config.xml: <preference name="android-targetSdkVersion" value="28" /> And this change allows to save files into the device using on Android 8, Android 9, Android 10 and Android 11. I did tests on Android emulator and physical devices working correctly.

The Android documentation recommends to change the API Level version to avoid the scoped storage feature on Android 10.

You get more information about scoped storage feature here: https://developer.android.com/training/data-storage/use-cases#opt-out-scoped-storage

I hope this information will be helpful.

breautek commented 3 years ago

I changed the android version in which the application is focused adding this tag in the config.xml: <preference name="android-targetSdkVersion" value="28" /> And this change allows to save files into the device using on Android 8, Android 9, Android 10 and Android 11. I did tests on Android emulator and physical devices working correctly.

The Android documentation recommends to change the API Level version to avoid the scoped storage feature on Android 10.

You get more information about scoped storage feature here: https://developer.android.com/training/data-storage/use-cases#opt-out-scoped-storage

I hope this information will be helpful.

This isn't a solution to anybody who wants to publish to the Google Play store as they require the target SDK to be set to >= 30.

israeltrejo commented 3 years ago

I changed the android version in which the application is focused adding this tag in the config.xml: <preference name="android-targetSdkVersion" value="28" /> And this change allows to save files into the device using on Android 8, Android 9, Android 10 and Android 11. I did tests on Android emulator and physical devices working correctly. The Android documentation recommends to change the API Level version to avoid the scoped storage feature on Android 10. You get more information about scoped storage feature here: https://developer.android.com/training/data-storage/use-cases#opt-out-scoped-storage I hope this information will be helpful.

This isn't a solution to anybody who wants to publish to the Google Play store as they require the target SDK to be set to >= 30.

You're right, it is necessary set API Level to 30 for Play Store but I forced the API Level to 30 testing again in Android 8, 9, 10 and 11 and everything worked fine.

This is the preference tag: <preference name="android-targetSdkVersion" value="30" />

Also, I tested that an application with this tag could be uploaded to Play Store Console and the file was accepted.

erisu commented 3 years ago

You're right, it is necessary set API Level to 30 for Play Store but I forced the API Level to 30 testing again in Android 8, 9, 10 and 11 and everything worked fine.

When running your tests, did you uninstall the app and its data from your device first? Should test the clean install use case, where a user has never had the app installed before.

israeltrejo commented 3 years ago

You're right, it is necessary set API Level to 30 for Play Store but I forced the API Level to 30 testing again in Android 8, 9, 10 and 11 and everything worked fine.

When running your tests, did you uninstall the app and its data from your device first? Should test the clean install use case, where a user has never had the app installed before.

Sure, I cleaned the data and uninstall the application before test in each Android version.