androidqtandroid-permissions

Qt for Android: How to allow app to write files to Documents folder


I'm trying to run a simple Qt Android app that creates a file in Documents folder.

The program is prettry simple:

#include <QApplication>
#include <QMainWindow>

#include <QLabel>
#include <QFile>
#include <QStandardPaths>

#include <fstream>

int main( int argc, char* argv[] )
{
    QApplication app(argc, argv);
    QMainWindow frame;

    QString status = "";

    auto folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
    auto fileName = folder + "/file.txt";
    // create a file:
    std::fstream file;
    file.open( fileName.toStdString().c_str(), std::ios_base::out );
    file << "Hello World!" << std::endl;
    file.close();
    if (QFile(fileName).exists())
        status += "Could create new file " + fileName + "\n";
    else
        status += "Could not create new file " + fileName + "\n";

    frame.setCentralWidget( new QLabel(status) );

    frame.show();
    return app.exec();
}

I'm using Qt 6.9. My manifest file has android.permission.READ_EXTERNAL_STORAGE and android.permission.WRITE_EXTERNAL_STORAGE permissions. I see Qt provides way to request permissions with QPermission but this is only for camera, location, bluetooth...

When I run this app on Android 9, it reports Could not create new file /storage/emulated/0/Documents/file.txt. Now, if I go to the application's information under Android, see the permissions, it has "Stockage" (french version, likelly "Storage" in english) permission here but unchecked. If I check it and run the app again, it reports Could create new file /storage/emulated/0/Documents/file.txt.

How can I programatically enable this permission to have my app be able to write to Documents folder without having to enable the permission manually?


Solution

  • Both READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE have protection level "dangerous", which means that, even if they are declared in the Android manifest file, they still have to be requested at runtime (see Android documentation). This classification of permissions was introduced in Android 6.0.

    The QPermission API for this is incomplete and there are several related issues on Qt Bug Tracker (136668, 110081, 110817), but there's loosely-documented unstable QtAndroidPrivate namespace containing requestPermission function that can be used like this:

    QtAndroidPrivate::requestPermission(permission).then(qApp,
        [](QFuture<QtAndroidPrivate::PermissionResult> future) {
            const auto status = future.result();
            // status is now QtAndroidPrivate::PermissionResult::Authorized or QtAndroidPrivate::PermissionResult::Denied
            // ...
        });
    

    (where permission is a QString containing permission string of particular permission)

    Note that QPermission API is a quite thin wrapper around QtAndroidPrivate. You can see in the source for QPermissions::Private::requestPermission that permission types Q{X}Permission are mapped to one or several permission strings by nativeStringsFromPermission and then passed to QtAndroidPrivate::requestPermissions (this one is undocumented but differs from QtAndroidPrivate::requestPermission only by accepting a QStringList and checking several permissions at once). Also, caching is implemented to track whether the permission is undetermined, i.e. hasn't yet been requested, which is handled on Qt side because underlying Android routines return that permission is denied if it hasn't been requested yet, but Qt wants to recognize this situation separately (if you want track this as well, you can implement it similarly to how it is done there via global g_permissionStatusHash).

    Also beware that if you plan to target newer Android versions, WRITE_EXTERNAL_STORAGE has no effect since Android 11 or higher and READ_EXTERNAL_STORAGE - since Android 13 or higher (see docs linked above). You can, however, since Android 4.4 create files by using document provider (see android tutorial), in particular ACTION_CREATE_DOCUMENT intent. You can use intents in Qt 6 via QAndroidIntent class and QtAndroidPrivate::startActivity function (though it's also unstable).