Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Write access to SD card in Android



  • Hi everyone,

    I am using Qt Creator Qt Creator 5.0.2.
    The compiler versions that i see in the projects tab (far left) are 6.1.3.

    I am developing a Qt widget application that requires a backup operation that includes copying image & video files from flash memory to SD card. Ideally i would like to save anywhere in an SD i want including root. I understand that this is not allowed by Android OS.

    Paths being used to save images & videos in flash are retrieved by QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) when it comes to deploying to Android which works fine. However, no matter how big flash memory is (e.g 1 TB) i still need to back up to external storage mediums including SD card & external USB drives since i save MJPG instead of H264 video which takes considerably more space.

    After searching for solutions i found this bug: https://bugreports.qt.io/browse/QTBUG-65824 but it is closed (not sure why).
    I also found this project: https://github.com/FalsinSoft/QtAndroidTools/ in github (not sure if it will do the job).

    In order for me to copy an entire tree of files & folders i use QDir::mkdir() and QFile::copy() .

    It works fine in a linux system while selecting (1) a local path or (2) a FAT32 USB drive as destination for the backup.

    I also read that i have to use the SAF in order to have write access to the SD card. Any idea how i can do that?

    I am targetting Android 11 (api 30). If it helps i could target another api version.

    I have been stuck in this for a few days now.

    Any help would be highly appreciated.

    Thanks & regards.
    Manos



  • Hi everyone,

    The application is a Qt Widget project targetting Android 11 version (api 30).

    I was able to find my way through by realizing that Android does not allow by definition write access to any location if it is not allowed by the operating system itself.

    A few links that helped me were:

    #1: https://stackoverflow.com/questions/28655181/calling-java-function-from-qt-c
    #2: https://doc.qt.io/qt-6/qjniobject.html
    #3: https://web.archive.org/web/20200217035702/http://schorsch.efi.fh-nuernberg.de/roettger/index.php/QtOnAndroid/JNI
    #4: https://developer.android.com/about/versions/11/privacy/storage

    I had to fix a few things since my Qt version is 6.1.3.

    I basically had to write a function that calls java functions through the abstraction class QJniObject in order get my hands on the writable paths.

    Paths for the flash memory in Android can be retrieved with QStandardPaths::writableLocation(QStandardPaths::StandardLocation type).

    However my problem was writing / copying files & dirs from flash to the SD card.

    So using the abstraction class above i was able to put together a function with the help of link #3.

    I had to change the names of some classes like QAndroidJniEnvironment to QJniEnvironment, QAndroidJniObject to QJniObject etc.

    Your .pro file does not require module 'androidextras' and you do not need to include QtAndroidExtras header.

    However i had to conditionally include:

    #if defined (Q_OS_ANDROID)
    // To be replaced by <QtAndroid> in Qt 6.2
    #include <QtCore/private/qjnihelpers_p.h>
    // Abstraction class
    #include <QJniObject>
    #endif

    The following function returns a QStringList of writeable paths. In my case i used it to embed both Android and Linux specific directories.

    // Return a list of writable paths
    QStringList getWritablePaths(void){
    
        QStringList paths;
    
        #if defined (Q_OS_ANDROID)
            QJniObject context(QtAndroidPrivate::activity());
    
            //QJniObject package = context.callObjectMethod("getPackageName", "()Ljava/lang/String;");
            //notice("Package name: %s", package.toString().toStdString().c_str());
    
            // Call a java function through the abstraction
            QJniObject dirs = context.callObjectMethod("getExternalFilesDirs", "(Ljava/lang/String;)[Ljava/io/File;", NULL);
    
            // Valid dirs exist
            if (dirs.isValid()){
    
                QJniEnvironment env;
    
                // Size of dirs found
                jsize size = env->GetArrayLength(dirs.object<jarray>());
    
                // Iterate through the dirs found and add them in the QStringList
                for (int ctr = 0; ctr<size; ctr++){
                   QJniObject dir = env->GetObjectArrayElement(dirs.object<jobjectArray>(), ctr);
                   paths.push_back(dir.toString());
                   notice("External directory found [%d]: [%s]", ctr, dir.toString().toStdString().c_str());
                }
            }
        #elif defined (Q_OS_LINUX)
            // Get the current working directory in Linux
            char cwd[128];
            getcwd(cwd, 127);
            paths.push_back(QString(cwd));
        #endif
    
        return paths;
    }
    

    Examples of path strings returned:
    Flash memory application specific path: "/storage/emulated/0/Android/data/org.qtproject.example.name_of_app/files"
    SD Card memory application specific path: "/storage/34FB-5BF5/Android/data/org.qtproject.example.name_of_app/files"

    One should not rely on the patterns seen above but rather use the function to get the actual path strings which will most probably change from maker to maker, across different android versions, different SD cards and also after re-formatting an SD card.

    Regarding SAF (Storage Application Framework) here is what I found: Qt supports it natively and transparently (i.e. for normal QDir/QFile operations) since https://code.qt.io/cgit/qt/qtbase.git/commit/?id=7d2d1eb9e051ad01dc5c5c3053c58364885bdc07 for "content://" URIs . However, for this particular case of backing up binary files to an app-specific location on external SD card it wasn't required.

    Regards
    Manos


Log in to reply