I'm trying to build an Android app which makes use of the NativeActivity facility of the NDK.
I'm having the following structure:
a bunch of native shared libraries installed in /system/vendor/<company>; I'm working
with a custom built Android image so there's no problem having the libraries there with
proper permissions and everything
a couple of applications using the NativeActivity that depend in turn on the libraries
mentioned above
The libraries installed in the /system/vendor and my applications use a couple of
configuration files. There's no problem reading them using the standard C API
fopen/fclose. But those libraries and my application also need to store some files
as the result of their operation, like configuration, some run-time parameters, calibration
data, log files etc. With the storing of the files there is a slight issue as I'm not allowed to write into /system/vendor/... (as the file system under "/system/..." is mounted read-only and I do not want to hack on that).
So what would be the best way to create and store those files and where would be the
best "conforming with Android" storage area ?
I've been reading a couple of threads in the android-ndk Google group and here on SO that mention either the internal application private storage or the external SD card, but as I do not have extended experience with Android I'm not sure what would be the proper approach. If the approach involves some specific Android API a small code example in C++ would be very helpful; I've seen a couple of examples involving Java and JNI (e.g. in this SO question) but I would like to stay away from that right now.
Also there seems to be a problem with using from C++ the native activity's
internalDataPath/externalDataPath pair (a bug that makes them be always NULL).
For relatively small files(application config files, parameter files, log files etc.)
is best to use the internal application private storage, that is /data/data/<package>/files.
The external storage if it exists at all (being it SD card or not) should be used for large files that do not need frequent access or updates.
For the external data storage the native application has to "request" the correct permissions in the application's AndroidManifest.xml:
<manifest>
...
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">
</uses-permission>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE">
</uses-permission>
</manifest>
For the internal application private storage fopen/fclose(or C++ stream equivalents if available) API could be used. Following example illustrates using the Android NDK AssetManager to retrieve and read a configuration file. The file must be placed into the assets directory inside the native application’s project folder so that the NDK build could pack them inside the APK. The internalDataPath/externalDataPath bug I was mentioning in the question was fixed for the NDK r8 version.
...
void android_main(struct android_app* state)
{
// Make sure glue isn't stripped
app_dummy();
ANativeActivity* nativeActivity = state->activity;
const char* internalPath = nativeActivity->internalDataPath;
std::string dataPath(internalPath);
// internalDataPath points directly to the files/ directory
std::string configFile = dataPath + "/app_config.xml";
// sometimes if this is the first time we run the app
// then we need to create the internal storage "files" directory
struct stat sb;
int32_t res = stat(dataPath.c_str(), &sb);
if (0 == res && sb.st_mode & S_IFDIR)
{
LOGD("'files/' dir already in app's internal data storage.");
}
else if (ENOENT == errno)
{
res = mkdir(dataPath.c_str(), 0770);
}
if (0 == res)
{
// test to see if the config file is already present
res = stat(configFile.c_str(), &sb);
if (0 == res && sb.st_mode & S_IFREG)
{
LOGI("Application config file already present");
}
else
{
LOGI("Application config file does not exist. Creating it ...");
// read our application config file from the assets inside the apk
// save the config file contents in the application's internal storage
LOGD("Reading config file using the asset manager.\n");
AAssetManager* assetManager = nativeActivity->assetManager;
AAsset* configFileAsset = AAssetManager_open(assetManager, "app_config.xml", AASSET_MODE_BUFFER);
const void* configData = AAsset_getBuffer(configFileAsset);
const off_t configLen = AAsset_getLength(configFileAsset);
FILE* appConfigFile = std::fopen(configFile.c_str(), "w+");
if (NULL == appConfigFile)
{
LOGE("Could not create app configuration file.\n");
}
else
{
LOGI("App config file created successfully. Writing config data ...\n");
res = std::fwrite(configData, sizeof(char), configLen, appConfigFile);
if (configLen != res)
{
LOGE("Error generating app configuration file.\n");
}
}
std::fclose(appConfigFile);
AAsset_close(configFileAsset);
}
}
}
Related
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');
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
I saw this package on Xamarin page. To access the file system I can use PCLStorage.FileStream.Current.localStorage to get the current root storage of the device but which folder it referring to on device: Is it the application private folder or somewhere else?
Can we access other folders e.g. On android, there DOCUMENT, DOWNLOAD folders?
PCLStorage source for LocalStorage:
#if ANDROID
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
#elif IOS
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
In Android this maps to the root of app's private storage, i.e.:
/data/data/{ApplicationID}/files
In iOS, this is mapped to the app's private Documents folder:
/data/data/{Package}/Documents
If your app requirements differ, you would need to mod the source of that project or provide your own platform code via a Xamarin.Forms dependency service.
I've had to upload a separate expansion file with my app (developed in Qt) in Google Play console. It's a simple rcc file. I have the name of file after the upload (it shows on the google console page). But I can't seem to find any info o how to get the shared-storage path (as mentioned) on the Android docs page (http://developer.android.com/google/play/expansion-files.html#StorageLocation).
I did come across this question (How to add android expansion files using Qt) where the user seems to have solved the issue but doesn't give any details on how to get the shared-storage path.
I've solved it:
I converted a qrc containing the large files to a binary using the qt rcc tool, then I used the following code to call the proper methods from the Qt Android Activity and retrieve the correct path to the expansion files:
QAndroidJniObject mediaDir = QAndroidJniObject::callStaticObjectMethod("android/os/Environment", "getExternalStorageDirectory", "()Ljava/io/File;");
QAndroidJniObject mediaPath = mediaDir.callObjectMethod( "getAbsolutePath", "()Ljava/lang/String;" );
QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;");
QAndroidJniObject package = activity.callObjectMethod("getPackageName", "()Ljava/lang/String;");
QString dataAbsPath = mediaPath.toString()+"/Android/obb/"+package.toString()+"/yourExpansionFileName.obb";
QAndroidJniEnvironment env; // Don't know what this is for ?
if (env->ExceptionCheck()) { env->ExceptionClear(); } // Or this...?
bool loadResource = QResource::registerResource(dataAbsPath);
Don't forget to add #include <QAndroidJniEnvironment> in your app (and QT += androidextras in pro file), and last add this permission to the manifest (as the expansion file is located inside the external storage):
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
I didn't check if the file was there or not before loading (every device I tested worked straight), but the android docs say you might need to download it manually. There is also some catch to uploading expansion files, you can only add them with the second apk submission (in Google play).
This code may be help you.
QStringList systemEnvironment = QProcess::systemEnvironment();
looking for
EXTERNAL_STORAGE=/storage/emulated/legacy
EXTERNAL_ADD_STORAGE=/storage/external_SD
SECONDARY_STORAGE=/storage/external_SD
...
This code worked for me:
QString s = QStandardPaths::writableLocation(QStandardPaths::movieLoaction);
QDir MyDir;
s = s + "/MyFolder"
MyDir.mkdir(s);
In your Android device you must check the storage permissions for your application. If not add this permission then run the application.
I want to use open alpr (automatic licences plate recognition) library in my android project. I compiled everything successfully and now it is time to use open alpr in app but...
to create Alpr class object properly I have to provide path to config file and path to runtime_data folder which contains some mandatory files needed by open alpr (ocr and trained data).
I tried something like:
Alpr alpr = new Alpr("eu", "android_assets/alpr.conf", "android_assets/runtime_data");
but Alpr.isLoaded() returns false which means that config or runtime_data have not been found.
Path to assets folder in project is: src/main/assets.
Can someone explain to me how path to "runtime_data" directory and "alpr.conf"
should looks to be visible by open alpr?
Thanks in advance.
I am not familiar with the specific library, but on newer Android devices (Android 6 and up), you can not rely on your application files residing under /data/data/your.package.name
The actual library name still includes the package name of your app, but also has some identifier appended to it in base64 format.
This identifier is unique per installation, and it will change if you uninstall and reinstall the app on the same device.
So, if your library needs to use a configuration file with a path to some other files, there are 2 options:
The right way:
Get the real address of your application files folder using Context.getFilesDir().
Unpack you files from the assets folder of the APK on the device using AssetManager.
Programmatically rewrite your configuration file with the path returned by getFilesDir().
The "hacky" but simpler way:
Use public storage to unpack your files.
You will need to add WRITE_EXTERNAL_STORAGE permission to your app, and unpack the assets files to the external storage.
For backwards compatibility this will be available under /sdcard folder on most Android devices, even with the latest Android version.
The second method is not recommended since using /sdcard directly is deprecated and strongly discouraged by Google.
Also, not all Android devices have /sdcard link to their public storage, but this is the only way to avoid dynamically editing the configuration file after installation.
Important note before you start implementing those steps. This library supports only arm CPU architecture. Good news is, most probably, your physical device is using arm architecture but to make sure just double-check it before implemting those steps.
I've recompiled this library to a new wrapper library. In original library, you need to manually configure openalpr.conf file and edit its content with correct path to your data directory. Manual configuration is cumbersome because since Android 5 multiple user accounts is supported and we can't simply hardcode data directory as /data/data/com.your.packagename/..... Because every user gets their symlink to data directory as /data/user/0/com.your.packagename/..... All those manual steps are gone in recompiled wrapper library.
Implementation
Add this in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Add the dependency into app module:
dependencies {
...
implementation 'com.github.mecoFarid:openalpr:1.0.0'
}
And you're done. Please check this sample app to get started with UI.
Troubleshooting:
If your target sdk is targetSdkVersion >= 24 and you're running your app on a device with Android API 24+ you'll get following error:
android.os.FileUriExposedException: file:///storage/emulated/0/OpenALPR/2019-09-21-01-32-13.jpg exposed beyond app through ClipData.Item.getUri()
To solve this error: you can add following lines into onCreate() of your Activity as a workaround or you may use this thread for offical solution:
if(Build.VERSION.SDK_INT>=24){
try{
Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
m.invoke(null);
}catch(Exception e){
e.printStackTrace();
}
}
TEST:
You can use this image to test your app.
"/data/data/yourpackagename" + File.separatorChar + "runtime_data"
+ File.separatorChar + "openalpr.conf";