I am concerned about two questions regarding how Qt works with Android directories.
For example, I want to download an archive via a library (QuaZip), but I need it to be downloaded along the path "/ storage / emulated / 0 / Android / data /", and then unzipped right there. But the problem is that the file is saved in the patch "/data/user/0/org.qtproject.example.NameApp". How can this be fixed? My code (it is unlikely that it works at all, but on Windows it would work for sure):
m_file.rename ("/storage/emulated/0/Android/data/cache.zip");
Can I open a file in Android via QProccess?
On your first question about Android/data r/w
On Android 11 Android/data is forbidden for app access without root, see docs1 and docs2.
For read/write to other locations:
The directory must exis or must be created (with QDir::mkpath() at once, or with QDir::mkdir() by subdirectories sequentally, because it can't create trees of directories).
QFile must be closed before rename.
App must have android.permission.READ_EXTERNAL_STORAGE and android.permission.WRITE_EXTERNAL_STORAGE permissions.
At last, approve file access permissions manually in Android apps settings after app is deployed from Qt Creator. And you also may have to launch app manually, because there were reports about different app behavior when launched from IDE and and when launched manually from device.
This code worked for me (Android 11, Qt 5.15.2):
bool result;
QDir dir("/storage/emulated/0");
qDebug() << dir;
result = dir.mkdir("cache");
qDebug() << "mkdir(\"cache\") success: " << result;
QFile m_file(dir.path() + "/cache/cache.txt");
result = m_file.open(QFile::WriteOnly);
qDebug() << "open() success: " << result;
m_file.close();
result = m_file.rename(dir.path() + "/cache/cache.zip");
qDebug() << "rename() success: " << result;
On your second question about QProcess
This code worked for me:
QProcess process;
process.start("touch", QStringList() << "/storage/emulated/0/new_text_file.txt");
result = process.waitForFinished(1000);
qDebug() << "waitForFinished() success: " << result;
qDebug() << "errorString(): " << process.errorString();
The file new_text_file.txt appeared on my device storage. If you specify the type of file you want to launch, I can answer more precise.
Related
I'm developing an app on android using Qt 5.15.0. My app creates a database and some other files using QStandardPaths::AppDataLocation. everything works fine from the code side: debug output makes me think the files are there and that the app is using them. The problem is that when I search from the PC inside the smartphone folders I cannot find the files created. So this makes me think I am not writing to the folder I'm expecting to.
This is my code:
appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir directory;
if (!directory.exists(appDataPath + QStringLiteral("/DATABASE")))
directory.mkpath(appDataPath + QStringLiteral("/DATABASE"));
if (!directory.exists(appDataPath + QStringLiteral("/DATA")))
directory.mkpath(appDataPath + QStringLiteral("/DATA"));
qDebug() << "Percorso: " << appDataPath << " dir exists:" << directory.exists(appDataPath + QStringLiteral("/DATABASE"));
QFile f( appDataPath + "/DATABASE/Prova.txt" );
if( f.open( QIODevice::WriteOnly ) )
{
f.write( "Ciao" );
f.close();
}
if( f.error() != QFileDevice::NoError )
qDebug() << QString("Error writing file '%1':").arg(appDataPath + "/DATABASE/Prova.txt") << f.errorString();
I also write a file that I read at startup and I can read from it so I assume the file is present and is not saved as temporary.
The output is:
D/MyApp(17849): Percorso: "/data/data/org.qtproject.example/files" dir exists: true
Now I suppose I can find my files in this directory inside the phone in this folder:
Questo PC\Samsung Galaxy J3 (2016)\Phone\Android\data\org.qtproject.example\files
But there are no directories or files inside. I bet I am missing some trivial things.
I am giving the app these permission:
android.permission.WRITE_EXTERNAL_STORAGE
android.permission.READ_EXTERNAL_STORAGE
Can someone help me?
Thanks in advance!
It seems that
QStandardPaths::AppLocalDataLocation, QStandardPaths::AppDataLocation
don't give the correct path to the app's file folder. I encountered the same issue with Qt 5.14.1. While QStandardPaths gave me
/data/user/0/com.mycompany.myapp/files
a call via a QAndroidJniObject to the java side gave me
/storage/emulated/0/Android/data/com.mycompany.myapp/files
Using the latter all directories and files were created correctly. I suggest to either try my work-around with
QtNative.activity().getExternalFilesDir(null).getAbsolutePath();
in an added java class and call it through a QAndroidJniObject or report this as a bug to Qt.
Nevertheless, there might be another solution to the problem I'm not aware of.
I try to embed the sqlite file generated by QML's Local Storage for Android. The code below works for desktop. Although There are sqlite and ini files in Android's assets folder, the app doesn't see the database file as default. How can I get it work?
The code in main.cpp:
QString customPath = "assets:/OfflineStorage";
QDir dir;
if(dir.mkpath(QString(customPath))){
qDebug() << "Default path >> "+engine.offlineStoragePath();
engine.setOfflineStoragePath(QString(customPath));
qDebug() << "New path >> "+engine.offlineStoragePath();
}
engine.clearComponentCache();
It has been solved here. The problem was copying sqlite and ini file to the right location.
I have problems creating and writing files to an Android device in Qt. I have unsuccessfully tried a few different examples.
This example should write to the internal storage's Download folder:
QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
qDebug() << path; // '/storage/emulated/0/Download'
qDebug() << "Exists? " << QFile::exists(path + "/401891.png"); // This is an existing image in the folder, returns true
QFile testFile(path.append("/Testing.txt"));
testFile.open(QFile::WriteOnly);
QTextStream out(&testFile);
out << "Hello";
testFile.close();
qDebug() << "Exists? " << QFile::exists(path + "/Testing.txt"); // Returns false
I get this warning:
Warning: QIODevice::write (QFile, "/storage/emulated/0/Download/Testing.txt"): device not open
What I am missing and how to fix it?
Seems like the permissions were not being applied although they were stated in Manifest.
Go to Settings -> Applications -> Application Manager
Find your application and ensure the 'Storage' permission toggle is 'On'.
You are wrong in a last line, it gives:
.../Download/Testing.txt/Testing.txt/
use
qDebug() << "Exists? " << QFile::exists(testFile.fileName()); // Returns true
I am trying to make an android application using qml and C++ on QtCreator and I am not able to deploy sqlite database to android :
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
QFile file(":/patients.db") ;
if (file.exists()) {
file.copy("./patients.db") ;
QFile::setPermissions("./patients.db",QFile::WriteOwner | QFile::ReadOwner) ;
} else qDebug() << "the file does not exist" ;
db.setDatabaseName("./patients.db");
patinets.db exist under the qrc tree in QtCreator and the code work on my development host (Linux ubunntu)
But on android, the debug message "the file does not exist" gets printed out.
Am I missing something here?! What is the correct way to do this?
Thank you.
It is helpfull if you check status return codes in each step of your 'deploying'. Both, file.copy() and QFile::setPermissions() have success indicating return values.
It is highly possibile, that alreadyfile.copy("./patients.db") failes, because on android you are not allowed to create any files in same directory where your binary/apk is contained.
You need to obtain the path to internal storage allocated for your app at runtime. Link to 'Saving Files' of Android docs.
You should obtain the correct path by means of QStandartPath (e.g. AppLocalDataLocation).
You could obtain a possible target directory and place your sqlite file there like this:
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
QFile file(":/patients.db") ;
QString patientDbPath;
if (file.exists()) {
patientDbPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
if (patientDbPath.isEmpty())
{
qDebug() << "Could not obtain writable location.";
return;
}
patientDbPath.append("/patients.db");
file.copy(patientDbPath) ;
QFile::setPermissions(patientDbPath ,QFile::WriteOwner | QFile::ReadOwner) ;
} else qDebug() << "the file does not exist" ;
db.setDatabaseName(patientDbPath);
I am currently trying to create a multiplatform application (targets are Windows and Android via ndk).
So the problem is in textures (as in topic of this post). It works great on windows, but in Android FreeImage_GetFileType returns FIF_UNKNOWN, despite the fact that I can read part of .png file manually from same file path (only PNG file magic, beceause then it contains some null bytes).
I use them this way (only part connected to this issue):
Texture::Texture(std::string fpath)
:Resource(fpath) //Resource makes fpath=/storage/emulated/0/+...+/+fpath (via reference)
{
std::ifstream file(fpath);
if(!file){ //this isnt fired both on android and windows (so it can find this file)
std::cout << "[error]: couldn't open " << fpath << "\n";
#ifdef ANDROID
LOGI("[error]: couldn't open %s", fpath.c_str());
#endif // ANDROID
}
std::string textureStr = std::string((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
#ifdef ANDROID /*this also works nice, prints
/storage/emulated/0/external_sd/Android/data/package/textures/textureUV.png
and �PNG
*/
LOGI("loading image: %s as %s", fpath.c_str(), textureStr.c_str());
#else
std::cout << "loading image " << fpath << ", " << textureStr.c_str() << "\n";
#endif // ANDROID
file.close();
batchId = textureCounter;
glGenTextures(1, &id);
bind(GL_TEXTURE_2D);
FREE_IMAGE_FORMAT format = FreeImage_GetFileType(fpath.c_str(), 0);
if(format == FIF_UNKNOWN) { //this one is triggered on Android only
#ifdef ANDROID
LOGI("Failed to read format %s", fpath.c_str());
#endif // ANDROID
std::cout << "Failed to read format # " << fpath.c_str() << "\n";
}
if(format == FIF_UNKNOWN) //this one is triggered on Android only too
format = FreeImage_GetFIFFromFilename(fpath.c_str());
if(format == FIF_UNKNOWN) { //and this one is triggered on Android only too
#ifdef ANDROID
LOGI("Failed to read format v2 %s", fpath.c_str());
#endif // ANDROID
std::cout << "Failed to read format v2 # " << fpath.c_str() << "\n";
}
So, why is it working on Windows but not on Android?
By the way, I have read/write access in AndroidManifest, and the textures are placed in assets folder and then extracted to sd card using AAsetManager from java (it is all before native code gets executed). I can also see thaht textures are stored correctly using adb shell cat /storage/emulated/0/external_sd/Android/data/package/textures/textureUV.png which prints the png file.
And I have to add that I have built FreeImage from source to static library. I made some changes (mosly about arm/neon stuff). But I have even tried some others people build of this library (from Linderdaum engine) with same effect (not working images type detection).