Closed HarelM closed 2 years ago
Hey guys any update on this I am not able to upload files from the download folder due to this storage change
I am getting file input stream in file transfer plugin to show progress other solutions like Media store API and SAF also not working for me
I am not sure which part of the cordova stack is responsible for this but setting an img src attribute to the file://
address of a stored image retrieved using this plugin and FileEntry.toURL()
used to work fine. However now:
<img src="FileEntry.toURL()">
results in:
Not allowed to load local resource: file:///storage/emulated/0/Android/data/...
<img src="FileEntry.toInternalURL()">
results in:
Failed to load resource: net::ERR_UNKNOWN_URL_SCHEME
I think this is the same as the issue over on https://github.com/apache/cordova-plugin-camera/issues/761
Starting November 1, 2021, updates to apps and games on Google Play will be required to target Android 11 (API level 30) or higher.
I have tried to apply a couple of solutions from this thread but it didn't work.
Also, I've tried manually adding the needed permission but 'android.permission.MANAGE_EXTERNAL_STORAGE' don't appear in AndroidManifest.xml
after I add to config.xml next part:
<config-file file="app/src/main/AndroidManifest.xml" mode="merge" parent="/manifest" xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
</config-file>
package.json
"cordova-android": "^9.1.0",
"cordova-js": "^5.0.0",
Starting November 1, 2021, updates to apps and games on Google Play will be required to target Android 11 (API level 30) or higher.
I have tried to apply a couple of solutions from this thread but it didn't work.
Also, I've tried manually adding the needed permission but 'android.permission.MANAGE_EXTERNAL_STORAGE' don't appear in
AndroidManifest.xml
after I add to config.xml next part:<config-file file="app/src/main/AndroidManifest.xml" mode="merge" parent="/manifest" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> </config-file>
package.json
"cordova-android": "^9.1.0", "cordova-js": "^5.0.0",
</edit-config>` [(source)](https://developer.android.com/training/data-storage/use-cases#if_your_app_targets)
I don't think anything will work until this plugin uses the new scoped storage APIs. This issue has the label "Help wanted" so if anyone feels like getting their hands dirty with Java that would be great. My time would be better spent migrating to Capacitor.
Does anyone has a solution for the problem with SDKVersion 30?
Does anyone has a solution for the problem with SDKVersion 30?
I don't but I can confirm that Capacitor's Filesystem plugin works great and uses all the new Android APIs under the hood i.e. you get a content://
URL that you can actually use when storing files.
The ground seems to be shrinking beneath Cordova's feet at an accelerating rate and there's no better time to migrate to Capacitor than now. It's really stable, is a pleasure to use and the migration should be fairly painless.
Anyone else who had a solution for that without capacitor?
I use externalDataDirectory
instead of externalRootDirectory
.
I use
externalDataDirectory
instead ofexternalRootDirectory
.
can you be more specific? can you show me your code?
@hanslbua
import { File } from '@ionic-native/file/ngx';
import { FileTransfer, FileTransferObject } from '@ionic-native/file-transfer/ngx';
.....
constructor(
private file: File,
private transfer: FileTransfer,
) {
}
....
async download(url: string, fileName: string): Promise<string> {
const directoryPath: string = this.isIOS ? this.file.documentsDirectory : `${this.file.externalDataDirectory}Download/`;
const fileTransfer: FileTransferObject = this.transfer.create();
const entry = await fileTransfer.download(url, `${directoryPath}${fileName}`);
return entry.toURL();
}
....
package.json
...
"@ionic-native/file": "^5.15.0",
"@ionic-native/file-transfer": "^5.30.0",
"cordova-plugin-file": "^6.0.2",
"cordova-plugin-file-transfer": "git+https://github.com/apache/cordova-plugin-file-transfer.git",
...
I addressed the issue where the application could not access a file shared into the application by the user by instead forking cordova-plugin-filepath: https://github.com/VIAVI-Solutions/cordova-plugin-filepath/commit/a4714209cfbd2bd8eebad902b8f5ef3e34fb8baa
Hey i have it like this:
window.resolveLocalFileSystemURL(file, resolveOnSuccess, function () { console.log("resolveLocalFileSystemURL Error"); });
function resolveOnSuccess(entry){
//new file name
let fileName = "test.jpg"
let myFolderApp = "app_media";
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSys) {
//The folder is created if doesn't exist
fileSys.root.getDirectory( myFolderApp,
{create:true, exclusive: false},
function(directory) {
entry.copyTo(directory, fileName, successCopy, function () { console.log("copyTo Error"); });
}
, function () { console.log("getDirectory Error"); });
},
function () { console.log("requestFileSystem Error"); });
}
Output is: getDirectory Error
Can anyone help me?
Starting November 1, 2021, updates to apps and games on Google Play will be required to target Android 11 (API level 30) or higher.
I have tried to apply a couple of solutions from this thread but it didn't work.
Also, I've tried manually adding the needed permission but 'android.permission.MANAGE_EXTERNAL_STORAGE' don't appear in
AndroidManifest.xml
after I add to config.xml next part:<config-file file="app/src/main/AndroidManifest.xml" mode="merge" parent="/manifest" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> </config-file>
package.json
"cordova-android": "^9.1.0", "cordova-js": "^5.0.0",
@Yura13 Is it generating Android target: android-30 ? because When I try to add "cordova-android": "^9.1.0" it shows Android target: android-29 not 30
I believe that until this plugin code adapts to the new android 30 requirements the only clean option is to store only locally and transfer data from and to other plugins that have adapted(if possible). For example, get the images from the camera plugin in base64 and store locally(cordova.file.dataDirectory).
@Topiya Yes. You probably need to change the next lines in config.xml:
<preference name="android-minSdkVersion" value="22" />
<preference name="android-targetSdkVersion" value="30" />
@Topiya Yes. You probably need to change the next lines in config.xml:
<preference name="android-minSdkVersion" value="22" /> <preference name="android-targetSdkVersion" value="30" />
@Yura13 I have already added above lines in config.xml. But still getting Android target: android-29
Verify installed sdk version on your computer via Android SDK
Also, try to run the next commands in terminal:
ionic cordova platform rm android
ionic cordova platform add android@9
I have already added sdk as above.
With Android 10 save to persistant has worked but i get this error:
Not allowed to load local resource: file:///storage/emulated/0/app_media/1637263763703.jpg
Not allowed sounds for me like a permission problem. Has someone else the same problem? How can i fix this?
If I add cordova-android 10.1.0 then only it shows Android target: android-30 while ionic cordova platform add android.
PS D:\Project> ionic cordova platform add android@10.1.0
cordova.cmd platform add android@10.1.0 Using cordova-fetch for cordova-android@10.1.0 Adding android project... Creating Cordova project for the Android platform: Path: platforms\android Package: test.test Name: test Activity: MainActivity Android target: android-30
cant acess the file :/ in sdk 30
I addressed the issue where the application could not access a file shared into the application by the user by instead forking cordova-plugin-filepath: VIAVI-Solutions/cordova-plugin-filepath@a471420
@chrisjdev Thank you. This is a great workaround for the time being.
i still cant acess my file , im using 👍
window.FilePath.resolveNativePath('file:///data/user/0/events.staff.booking.app/cache/FB_IMG_1637505177709.jpg', successCallback, errorCallback); function successCallback(s){ console.log("sucess:")+s; } function errorCallback(e){ console.log("error:")+e;
but the the file path isent acessible , i get no acessible path only console log "sucess:"
i still cant acess my file , im using +1
window.FilePath.resolveNativePath('file:///data/user/0/events.staff.booking.app/cache/FB_IMG_1637505177709.jpg', successCallback, errorCallback); function successCallback(s){ console.log("sucess:")+s; } function errorCallback(e){ console.log("error:")+e;
but the the file path isent acessible , i get no acessible path only console log "sucess:"
After making the change to the FilePath.java in plugins folder, have you tried removing and adding the Android platform?
i did, but i just realise that if i run window.open on my path : "file:///data/user/0/events.staff.booking.app/cache/FB_IMG_1637505177709.jpg" .. the image opens ... i just cant get it to upload to my server dont know why i get upload code:1 ... using file-tranfere plugin ...
Addressing a few comments over the past few days:
but the the file path isent acessible , i get no acessible path only console log "sucess:"
You're only getting "success:
because that's the only thing you're logging out.
Instead of doing: console.log("sucess:")+s;
I think you mean:
console.log("sucess:", s);
cant acess the file :/ in sdk 30
The root /
folder is protected. You can only access files under your application internal directory (See the File system directory table. Anything that isn't explicitly marked as external in this table are internal directories.
Not allowed to load local resource: file:///storage/emulated/0/app_media/1637263763703.jpg
Starting with cordova-android@10, cordova-android uses WebViewAssetLoader by default and disables local file system access as recommended by the android docs. This is because this option is deemed insecure, but was a critical component that Cordova that depended on with on other alternative until Android introduced the WebViewAssetLoader. Using the WebViewAssetLoader also overcomes some other same-origin issues as requests appear to come from a true origin rather than the filesystem which has a null
origin. More information on this change in our blog release notes.
This also means you can't use use file system urls directly like before. I think I might have to make a blog post about this but you can still read the file (assuming you have access to that file) as a blob and use Object URLs.
Alternatively, if you want the previous cordova-android behaviour, you can enable the AndroidInsecureFileModeEnabled
preference in your config.xml
file.
This is not related to the cordova-plugin-file, so I would respectfully ask to refrain from posting more on this. If you have further questions on this, you can reach reach out to our Slack community. (Link is a temporary link since the main link is down at the time of writing...)
MANAGE_EXTERNAL_STORAGE
Note that adding the MANAGE_EXTERNAL_STORAGE permission may work for you but it also may limit your ability to deploy to the Google Play store. This permission will require justification and Google reserves the right to block your app from the play store if they feel like your app don't need this permission.
@breautek I have added AndroidInsecureFileModeEnabled preference in config.xml but it is not working for me.
Thank you for the explanation @breautek. My issue was that I wasn't using WebViewAssetLoader. Simply replacing file://
with https://localhost
in my JavaScript allowed me to access my files from the webview in Android 11.
@brunoalex are you able to access https://localhost/data/user/0/events.staff.booking.app/cache/FB_IMG_1637505177709.jpg
in your JavaScript?
code :
window.resolveLocalFileSystemURL(cordova.file.externalRootDirectory, function(dir) { dir.getFile("test.pdf", {create:true}, function(file) { file.createWriter(function(fileWriter) { fileWriter.write("byteArrays data"); alert('Message', 'File Downloaded Successfully'); }, function(){ alert('Error!', "Unable to save the file"); }); },function(e){ alert('Error!', e); }); },function(e){ console.log(e); });
@alex-steinberg i can acess the file, but the funtions i used to upload the file after selecting from the galery to the server arent working in sdk30.. "ft.upload(imageURI, server, function(r)" ...
@alex-steinberg i can acess the file, but the funtions i used to upload the file after selecting from the galery to the server arent working in sdk30.. "ft.upload(imageURI, server, function(r)" ...
I have a similar problem.. my app downloads and unzips images in ///storage/emulated/0/Android/data/myappid/files/ but the app cannot access the images by javascript or by code ex. <img src="///storage/emulated/0/Android/data/myappid/files/image.png">
looks very confused
we are facing a similar issue where existing cordova implementation to read a file from downloads folder is working but not in android 11. Have tried all possible changes suggested to use fileentry object to get reference and then copy to a location where application has access to but read itself is failing. Cordova version used is 9.x and cordova-file-plugin has been used to read file. Required feature is to allow user to select a file from device and upload it. Upload to server is in base64 format. Kindly advise
@alex-steinberg - can you please share your code snippet and clarify whether you are using cordova 9 or cordova 10?
@alex-steinberg - We have set targetSdkVersion to 30 in config.xml file and created build using platform 9.1.0.
Below code works fine in lower android version (till android 10). In android 11 while reading a pdf file from download loaction, we get error as GET file:///storage/emulated/0/Download/Test.pdf net::ERR_ACCESS_DENIED
this.fileChooser.open() .then(uri => { let fileName = null; let fileExtension = null; let fileType = null; let isvalid = true; this.filePath.resolveNativePath(uri) .then(filePath => { return this.base64EncodeFile(filePath); }).then((base64Data: any)=>{ console.log(base64Data); }) .catch(err => console.log(err)); }) .catch(e => console.log(e)); }
base64EncodeFile(src){ return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.onload = function () { let reader = new FileReader(); reader.onloadend = function () { let result = { base64: reader.result, size: xhr.response.size, type: xhr.response.type } resolve(result); } reader.readAsDataURL(xhr.response); }; xhr.open('GET', src); xhr.responseType = 'blob'; xhr.send(); }); }
We have also tried below code
To copy from download folder to internal storage. It gives {"code":1,"message":"NOT_FOUND_ERR"} error
pathsrc : file:///storage/emulated/0/Download/Test.pdf
fileName : Test.pdf
this.file.copyFile(pathsrc, fileName, this.file.dataDirectory, fileName). then((result) => { console.log('file copied :' + result); }).catch(err => console.log("copied err "+ JSON.stringify(err)));;
Below code is using copyTo method, it gives error as FileError {code: 1}
window.resolveLocalFileSystemURL(pathsrc, function (fileSystem) {
fileSystem.getFile(fileName, {
create: false, // try true first to make sure that there are no other problems around
exclusive: false
}, function (fileEntry) {
window.resolveLocalFileSystemURL(window.cordova.file.externalDataDirectory , function (newFileEntry) {
fileEntry.copyTo(newFileEntry, fileName, function (result) {
console.log("save successfully:", result);
}, function (err) {
console.log("err-fileEntry.copyTo: ",err);
});
}, function (err) {
console.log("err-window.resolveLocalFileSystemURL: ",err);
});
}, function (err) {
console.log("err-fileSystem.getFile: ",err);
});
}, function (err) {
console.log("err-resolveLocalFileSystemURL: ",err);
});
I am trying to write file using following code:
this.file.checkDir(this.directorypath, 'AppFolder').then((dirExists) => {
this.file.writeFile(this.downloadDirectory, update_file + '.'+saveExtention, fileData, { replace: true }).then((fileEntry) => {
let mimeTestType = mimeType;
let path = fileEntry.nativeURL
this.openingFile(path, mimeTestType)
resolve(true)
}).catch((ex) => {
resolve(true)
console.error('Error')
})
}, (response) => {
this.file.createDir(this.directorypath, 'AppFolder', true).then((response) => {
this.file.writeFile(this.downloadDirectory, update_file + '.'+saveExtention, fileData, { replace: true }).then((fileEntry) => {
let mimeTestType = mimeType;
let path = fileEntry.nativeURL
this.openingFile(path, mimeTestType)
resolve(true)
}).catch((ex) => {
resolve(true)
console.error('Error')
})
})
})
});
This works till android 10 but not in android 11, it throws error.
This simple example app should shed some light on some of the issues raised here. At the very least, it would make a good place to continue the conversation around using files on Android 11 Cordova apps since, as has been mentioned, the problems experienced here aren't caused by the cordova-plugin-file
plugin. cc @siddharthaPatil @danicarla @brunoalex
With cordova@10.0.0
and cordova-plugin-file@6.0.2
, tested on android version from 12 to 8 on emulator, all version working.
config.xml
<widget ..... xmlns:android="http://schemas.android.com/apk/res/android">
...
<edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application">
<application android:usesCleartextTraffic="true" android:requestLegacyExternalStorage="true"/>
</edit-config>
...
window.resolveLocalFileSystemURL
, DirectoryEntry.getFile(....)
together with fileEntry.createWriter
can write to xx/Download/yyy
successfully on every android version.
Without android:requestLegacyExternalStorage="true"
, API 29 ( android 10 ) would fail to write, other version still work.
Add code below to AndroidManifest.xml, and you can write file to storage root directory like cordova.file.externalRootDirectory:
<manifest xmlns:tools="http://schemas.android.com/tools">
...
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage"/>
<application android:requestLegacyExternalStorage="true">
...
</manifest>
You can use this method:
var folderpath = cordova.file.externalRootDirectory + "Download";
window.resolveLocalFileSystemURL(folderpath, function (dir) {
dir.getFile(fileName, { create: true, exclusive: false }, function (fileEntry) {
fileEntry.createWriter(function (writer) {
writer.onwrite = function (writing) {
console.log("WRITING...");
};
writer.write(fileData);
console.log("SUCCESS");
}, function () {
console.log("FAIL");
});
});
});
Tested on Android 11
Add code below to AndroidManifest.xml, and you can write file to storage root directory like cordova.file.externalRootDirectory:
<manifest xmlns:tools="http://schemas.android.com/tools"> ... <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage"/> <application android:requestLegacyExternalStorage="true"> ... </manifest>
Tested on Android 11
This isn't standard behaviour or it works because the build isn't targeting API 30 or later as the Android docs explicitly states WRITE_EXTERNAL_STORAGE when targeting API 30 / Android 11 or later. Additionally when targeting API 30, scoped storage is enforced and requestLegacyExternalStorage is ignored.
This is becoming a bit messy :)
From updating from SDK v30 upwards the APP breaks exactly by merely reading the file system (cordova v11
and cordova-android v10.1.2
)
jquery.min.js:2 Not allowed to load local resource: file:///android_asset/www/json/anomalies.json
$.getJSON(cordova.file.applicationDirectory + 'www/json/anomalies.json', function (data) {
console.log(data)
})
1 - @breautek just confirm me please, if I remember correctly the issue regarding the transition from SDK 29 upwards was merely related with writing content to the device file system, right?
2 - @alex-steinberg mentions replacing file://
by http://localhost
. Does this work? If yes, shouldn't cordova.file.applicationDirectory
be updated accordingly?
My package.json:
config.xml:
@breautek I was reading now your post upwards, too many characters to go through :) and reading also your blog post
Ok, file://
is supposed to be insecure, but my previous question remains: is not possible to adapt the keys inside cordova.file
to https://localhost
? What am I missing?
@jfoclpf You're using a jquery API that presumably uses an XHR request to fetch data from a file://
url. This API is not related to the file plugin at all.
The webview doesn't have direct access to file://
unless if AndroidInsecureFileModeEnabled
preference is enabled. Enabling this will make Cordova uses the same filesystem based strategy for the webview, which is the same behaviour in cordova-android@9 and older. If AndroidInsecureFileModeEnabled
is off (the default value), then the webview is handled through a WebViewAssetLoader
, which is a way of treating your web app as if it was being loaded from a secure https
site, enabling features that requires a secure context. But this also means that the webview and standard browser features don't have direct access to the file system.
While the browser itself doesn't have direct access to the FS, native plugins still does, so if you used the actual cordova plugin API, cordova.file.applicationDirectory + 'www/json/anomalies.json
will be readable.
mentions replacing file:// by http://localhost. Does this work? If yes, shouldn't cordova.file.applicationDirectory be updated accordingly?
http://localhost
points to the asset directory, which is effectively the equivalent of file://android_asset/www
. The http://
version of the path will work in the webview and other browser APIs. But your real issue is that you're mixing APIs together. No, cordova.file.applicationDirectory
shouldn't be updated because that's part of the File plugin API, which still uses the file://
protocol, as that is what used on the native side. The cordova.file.applicationDirectory + 'www/json/anomalies.json'
path will resolve into a proper path that the file plugin can read.
Here's an untested example (may have syntax errors or a function name might be wrong, going off memory...)
resolveLocalFilesystemURL(cordova.file.applicationDirectory + 'www/json/anomalies.json', (fileEntry) => {
fileEntry.file((file) => {
let reader = new FileReader();
reader.onloadend = () => {
console.log('JSON String', reader.result);
};
reader.readAsText(file);
});
}, (error) => {
console.error('resolve error', error);
});
If you're using the WebViewAssetLoader, the latest version of this plugin as a fileEntry.toURL()
API that also produces a http(s)://
friendly URLs for standard browser APIs to consume.
Came to this issue repeatedly when searching for a way to fix opening files on Android that broke since skd 29. Updated my util to make it work for devices running Android 11 with sdk 33.
export async function openFile(remoteUrl: string, fileName: string, mimeType: string) {
if (!isCordova(window)) {
window.open(remoteUrl, '_blank');
return;
}
const transfer = FileTransfer.create();
const extension = mime.extension(mimeType);
const devicePath = `${File.dataDirectory}${fileName}.${extension}`;
await transfer.download(remoteUrl, devicePath);
const fileEntry = await File.resolveLocalFilesystemUrl(devicePath);
await FileOpener.open(fileEntry.nativeURL, mimeType);
}
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?