Cordova File Plugin: SECURITY_ERR - android

I have a hybrid app in production (iOS/Android) that uses Cordova plugins. I have one Android user who is getting a SECURITY_ERR when using the File plugin. This seems to be happening on a call to writeFile(). The file path I'm writing to for Android is externalRootDirectory.
Can anyone help me understand why 1 user (out of 300-400) would have this problem? The user is on Android 6.0.1 if that helps.
Some code is below. The error I'm getting is [Error creating file] – Error Msg: [SECURITY_ERR], so the .writeFile() catch is being hit in this case.
//Handle Native download
if (this.appConfig.isNative) {
this.loggingService.debug("Starting to create native file");
//Get base file path for android/ios
let filePath = (this.appConfig.isNativeAndroid) ? this.file.externalRootDirectory : this.file.cacheDirectory;
//Write the file
this.file.writeFile(filePath, fileName, data, { replace: true })
.then((fileEntry: FileEntry) => {
this.loggingService.debug("Created file: " + fileEntry.toURL());
//Open with File Opener plugin
this.fileOpener.open(fileEntry.toURL(), data.type)
.then(() => this.loggingService.debug('File is opened'))
.catch(e => this.loggingService.error('Error openening file', e));
})
.catch((err) => {
this.loggingService.error("Error creating file", err);
throw err; //Rethrow - will be caught by caller
});
}

I was able to figure this one out. Looks like after Android 6.0 certain permissions must be requested during use of the app (not just at install time). This is also alluded to in the Cordova File plugin docs, under Android Quirks.
Marshmallow requires the apps to ask for permissions when reading/writing to external locations. By default, your app has permission to write to cordova.file.applicationStorageDirectory and cordova.file.externalApplicationStorageDirectory, and the plugin doesn't request permission for these two directories unless external storage is not mounted. However due to a limitation, when external storage is not mounted, it would ask for permission to write to cordova.file.externalApplicationStorageDirectory.
So on Android 6.0+, when writing a file to disk the Cordova File plugin will display a prompt similar to:
Allow APP_NAME to access photos, media and files on your device?
If the user selects Deny to this request, then the Cordova file write will get this SECURITY_ERR, even if the app requests this permission at install time.

Related

Write file to directory with allowed access on Android

I want to export a save file so the user can later import it into the app again later, but how do I allow exporting to a directory outside the app dir (/Android/com.app/files)
if (!(await Permission.storage.request().isGranted)) return;
final String? exportPath = await FilePicker.platform.getDirectoryPath();
if (exportPath == null) return;
File exportFile = await File(
"$exportPath/zs_tracker_data.sav",
).create(recursive: true);
This code is backed by these gradle properties:
android.useAndroidX=true
android.enableJetifier=true
With compileSdkVersion 33 selected, and these permissions:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
All of these above allows me to get a path, and then get this prompt:
However, even when I press 'Allow', I am not allowed to touch that directory and get: Unhandled Exception: FileSystemException: Cannot create file, path = '/storage/emulated/0/Backups/zs_tracker_data.sav' (OS Error: Operation not permitted, errno = 1)
I've considered allowing MANAGE_EXTERNAL_STORAGE, but then my app won't be allowed on Play Store, since the only reasons it needs storage permissions are for creating backups and importing backups. I've also seen this post: Flutter read/write to external storage with Android SDK 30+, but the answer blows over my head completely and seems horribly over complex for my simple backup export/importing...
How do I go about this in a decently simple way? I just want to be able to save the exported file somewhere where the user can easily find it
Maybe use a totally different approach which doesn't require any permissions. Use sharing to allow a user to choose whatever a destination she/he desires. For example using share_plus plugin:
Share.shareFiles(['<path to file>'], text: 'Export file');

Unable to access android file system using Angular and Cordova

I have been trying to read files or write files in Android file system. My project is developed in AngularJS and converting it to APK using Cordova. When I install the app, it does not ask for file system permissions nor it is able to read or write files. It works in IDE but not in mobile. What can be the solution ?
You should call checkPermission and requestPermissions for WRITE_EXTERNAL_STORAGE and READ_EXTERNAL_STORAGE.
import { AndroidPermissions } from '#awesome-cordova-plugins/android-permissions/ngx';
constructor(private androidPermissions: AndroidPermissions) { }
this.androidPermissions.checkPermission(this.androidPermissions.PERMISSION.WRITE_EXTERNAL_STORAGE).then(
result => console.log('Has permission?',result.hasPermission),
err => this.androidPermissions.requestPermission(this.androidPermissions.PERMISSION.WRITE_EXTERNAL_STORAGE)
);
this.androidPermissions.requestPermissions([this.androidPermissions.PERMISSION.READ_EXTERNAL_STORAGE, this.androidPermissions.PERMISSION.WRITE_EXTERNAL_STORAGE]);
See https://github.com/NeoLSN/cordova-plugin-android-permissions

Unable to resolve filesystem path when trying to access Documents folder in a physical device with ionic/capacitor

I am developing a function to catch and read KML files, and another function to unzip and save these files that are inside of KMZ files (basically a zip).
In the first attempt I could not access it because an error told me that I don't have de right permissions, but according to capacitor documentation and after add android:requestLegacyExternalStorage="true" in the AndroidManifest.xml, I could obtain the right permission to access the device files. After that I had to filter the URI using the function FilePath.resolveNativePath(path); from the FilePath. The original path is generated by fileChooser.open().then(uri=>{}) and the URI is the root path to the file.
After some tests with an Android emulator using API 29/v10 and API 30/v11 and then a physical Android device with API 29/v10, I was able to make the functions work, but in physical device there is a specific folder, Documents, that is inaccessible and generate a error:
"ERROR Error: Uncaught (in promise): Object: {"code":0,"message":"Unable to resolve filesystem path."}".
Page.ts:
selectFile(): Promise<void>{
if (this.platform.is('cordova')) {
return this.fileChooser.open().then(uri => {
return this.filePath.resolveNativePath(uri).then(uri => {
let filename = uri.split('/').pop();
this.layerName = filename;
this.fileURI = uri;
});
});
}
}
Edits
URI before the edition : content://com.android.externalstorage.documents/document/home%3Apoligono.kml.
URI after the edition : error occurs after run the function FilePath.resolveNativePath(uri);. This happens only in this specific folder, in the physical device.
Folder Documents:

Nativescript: How to save a file in external storage (SD card)

After a lot of googling, and a lot of tries with "out-of-context" code, I'm begging you to help me.
I'm trying to figure out if it's possible to write on the external storage with Nativescript. I'm not interested to write within the application context. So what the docs shows, it's not what i'm looking for.
I've managed to achieve this from a thread on the Nativescript forum:
android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS).toString();
It works, it gives me a path, but when I have this path I have no clue of what to do with it. How to create a file inside that path, read it etc.
What I need to achieve is to create a folder that both the user and the application can easily access. The user should be able to access this folder with the builtin files explorer.
The application runs on Angular.
I really struggled with this one on Android device and in the end it was due to:
Not making sure the required permissions has been granted by the user in the app
Using "Android File Transfer" on my Macbook to verify the files have been created and to download them
tns info
nativescript 6.0.3
tns-core-modules 6.0.7
tns-android 6.0.2
tns plugins
nativescript-permissions 1.3.7
example code
import * as fs from "tns-core-modules/file-system"
...
// First get the required permissions
// Note: this permissions should also be in your AndroidManifest.xml file as:
// <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
const permissions = require('nativescript-permissions')
permissions.requestPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
.then(() => {
console.log('Required Android permissions have been granted');
})
.catch(() => {
console.error('Required Android permissions have been denied!');
});
// Get the publicly accessable Downloads directory path
const sdDownloadPath = android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS).toString()
console.log('sdDownloadPath: ' + sdDownloadPath)
// Get a specific folder in that path (will be created if it does not exist)
const myAppFolder = fs.Folder.fromPath(fs.path.join(sdDownloadPath, 'myApp'))
console.log('myApp path: ' + myAppFolder.path)
// Get a file in that path (will be created if it does not exist)
// Note: In this case we try to get a unique file every time this code is run
let date = new Date()
date = date.toISOString().replace('.', '')
const myFile = myAppFolder.getFile(`myfile_${date}.txt`)
console.log('myFile path: ' + myFile.path)
// Write some data to this new file
myFile.writeText('Hello duder 123')
.then(() => {})
.catch((err) => console.log(`Error writing to file: ${err}`))
// Try and read back the data that was written
myFile.readText()
.then((res) => {
console.log(`Text read back: ${res}`)
}).catch((err) => {
console.log(err.stack);
});
// List all files in the myApp folder
myAppFolder.getEntities()
.then((entities) => {
// entities is array with the document's files and folders.
entities.forEach((entity) => {
console.log(entity)
});
}).catch((err) => {
console.log(err.stack);
});
android file transfer issue
One problem I wasted a lot of time on was that I could see the files with getEntities() but could not see them when using the 3rd party tool "Android File Transfer (AFT)" on Mac. I eventually stumbled across "Android Studio's -> Device File Explorer" and could see all my created files and folders with it so realised the issue is with AFT.
I now make use of Airdroid to browse and download device files.
applicable reference
https://docs.nativescript.org/angular/ng-framework-modules/file-system
(angular docs but relevant to nativescript-vue as well)
Do you know your external(sd card) path?
If it is like /storage/emulated/0, then you could try this to create a folder or file.
import * as fs from "tns-core-modules/file-system";
let externalPath= fs.path.join(android.os.Environment.getExternalStorageDirectory().getAbsolutePath().toString());
//Create a folder with known path
var folder: fs.Folder = fs.Folder.fromPath(sdCardPath+"/test");
//Create a file
var testFile: fs.File = folder.getFile("test.txt");
console.log("Path " + folder.path)
User should be able to access this fold and file. It is in device internal storage which is "external" folder.
I still try to figure out how to get access to sd card but hope above code work for you.
I have the same issue and finally solved it by adding android:requestLegacyExternalStorage="true" inside the AndroidManifest.xml file
follow the thread here

Phonegap how to download and open pdf file in android app

recently i am working on a android apps build in phonegap, i am quite new with mobile development and this i smy first appsbut, and i am stuck when try to download some documents (pdf, doc, jpg) to local storage and open it, after search through google and i tried to follow this solution.
I followed the exact code in the example and below is how i call the plugins:
window.plugins.downloader.downloadFile(url,'/sdcard/download/', 'test.pdf', true, downloadOkCallbak, downloadErCallbak);
window.plugins.pdfViewer.showPdf('/sdcard/download/test.pdf');
The url is my remote file, when i execute it, i got error: "TypeError: window.plugins is undefined". Anyone help is appreciated.
[update] My code of showPdf function:
public String showPdf(String fileName) {
File file = new File(fileName);
if (file.exists()) {
try {
Uri path = Uri.fromFile(file);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(path, "application/pdf");
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
//intent.setData(Uri.parse(fileName));
this.ctx.startActivity(intent);
return "";
} catch (android.content.ActivityNotFoundException e) {
System.out.println("PdfViewer: Error loading url "+fileName+":"+ e.toString());
return e.toString();
}
}else{
return "file not found";
}
}
[Update 2] I got below error in web console: Uncaught ReferrenceError: LocalFileSystem is not defined at ...
What could be the issue?
I had the same problem as the OP and many unsuccessful approaches later I came to the following solution (tested with Cordova 3.1.0-3.3.0 & Samsung Galaxy S3 & iOS 7):
Solution
First, get the file by requesting the file system and then download it from your server with fileTransfer.download(). After completing the download call the FileOpener (which will open a pop up where to choose from apps like Adobe Reader). Note: Make sure to include the FileTransfer feature via CLI/Terminal: cordova plugin add org.apache.cordova.file
Navigate to your project folder
Open CLI/Terminal and type in: cordova plugin add https://github.com/don/FileOpener.git
After successful download and storage of your file on the device (see above) you open it with: window.plugins.fileOpener.open("file:///sdcard/Android/data/com.example.application/document.doc") or the appropriate path on iOS.
That's it! Good luck!
Make sure you've had these lines in your manifest.xml :
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
You can find help here How to download a pdf file in Android?
Make sure
You should install git and install file plugins(cordova 3.0+)
File and file transfer plugin
$ cordova plugin add org.apache.cordova.file
$ cordova plugin add org.apache.cordova.file-transfer
Ps. Try plugin fileOpener2
https://build.phonegap.com/plugins/1117
Try this to open any kind of documents from URL using following steps:
install this plugin : cordova plugin add https://github.com/ti8m/DocumentHandler
use this code :
handleDocumentWithURL(function() { console.log('success'); }, function(error) { console.log('failure'); if (error == 53) { console.log('No app that handles this file type.'); } }, 'http://www.example.com/path/to/document.pdf');
It works for me both on Android and IOS. I used it for open images and PDF files.
Android : It opens files using system apps if available, otherwise it give an error, which you can handle.
IOS : It opens files in popup like view with Done button and Option button.
It doesn't show your docs URL.
Source is available here : https://github.com/ti8m/DocumentHandler

Categories

Resources