From 95ee8d3bc728ba2338fceceace393e4c6b35bf2b Mon Sep 17 00:00:00 2001 From: Assam Boudjelthia Date: Sat, 25 Feb 2023 17:37:43 +0200 Subject: [PATCH] Android: fix and document QStandardPaths behavior on different versions Partially revert e1440dd7bc1a5da9a536f88b9733d04ec8fa6e61 for Android versions below 11 which could take advantage of the manifest flag android:requestLegacyExternalStorage. And for other newer versions avoid returning them while adding a note to the docs about this behavior. Fixes: QTBUG-108013 Fixes: QTBUG-104892 Task-number: QTBUG-81860 Change-Id: I10851c20e2831bddaa329164c941e2ae71f0a497 Reviewed-by: Ville Voutilainen Reviewed-by: Rami Potinkara (cherry picked from commit 81a748efb742092f5a0a1c33b8340478e52cc79f) Reviewed-by: Qt Cherry-pick Bot --- src/corelib/io/qstandardpaths.cpp | 17 +++-- src/corelib/io/qstandardpaths_android.cpp | 76 ++++++++++++++++++++--- 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/src/corelib/io/qstandardpaths.cpp b/src/corelib/io/qstandardpaths.cpp index ec8e6899e4c..a20c50e096c 100644 --- a/src/corelib/io/qstandardpaths.cpp +++ b/src/corelib/io/qstandardpaths.cpp @@ -250,7 +250,7 @@ using namespace Qt::StringLiterals; \li "/files" \li "/Documents/Desktop" \row \li DocumentsLocation - \li "/Documents", "//Documents" + \li "/Documents" [*], "//Documents" \li "/Documents" \row \li FontsLocation \li "/system/fonts" (not writable) @@ -259,13 +259,13 @@ using namespace Qt::StringLiterals; \li not supported (directory not readable) \li not supported \row \li MusicLocation - \li "/Music", "//Music" + \li "/Music" [*], "//Music" \li "/Documents/Music" \row \li MoviesLocation - \li "/Movies", "//Movies" + \li "/Movies" [*], "//Movies" \li "/Documents/Movies" \row \li PicturesLocation - \li "/Pictures", "//Pictures" + \li "/Pictures" [*], "//Pictures" \li "/Documents/Pictures", "assets-library://" \row \li TempLocation \li "/cache" @@ -280,7 +280,7 @@ using namespace Qt::StringLiterals; \li "/cache", "//cache" \li "/Library/Caches" \row \li GenericDataLocation - \li "" + \li "" [*] or "//files" \li "/Library/Application Support" \row \li RuntimeLocation \li "/cache" @@ -292,7 +292,7 @@ using namespace Qt::StringLiterals; \li "/files/settings" (there is no shared settings) \li "/Library/Preferences" \row \li DownloadLocation - \li "/Downloads", "//Downloads" + \li "/Downloads" [*], "//Downloads" \li "/Documents/Downloads" \row \li GenericCacheLocation \li "/cache" (there is no shared cache) @@ -328,6 +328,11 @@ using namespace Qt::StringLiterals; \note On Android, reading/writing to GenericDataLocation needs the READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE permission granted. + \note [*] On Android 11 and above, public directories are no longer directly accessible + in scoped storage mode. Thus, paths of the form \c "/DirName" are not returned. + Instead, you can use \l QFileDialog which uses the Storage Access Framework (SAF) + to access such directories. + \note On iOS, if you do pass \c {QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).last()} as argument to \l{QFileDialog::setDirectory()}, a native image picker dialog will be used for accessing the user's photo album. diff --git a/src/corelib/io/qstandardpaths_android.cpp b/src/corelib/io/qstandardpaths_android.cpp index 4d5664d3ff8..e058f379c28 100644 --- a/src/corelib/io/qstandardpaths_android.cpp +++ b/src/corelib/io/qstandardpaths_android.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qstandardpaths.h" @@ -36,6 +36,48 @@ static inline QString getAbsolutePath(const QJniObject &file) return path.toString(); } +/* + * The root of the external storage + * + */ +static QString getExternalStorageDirectory() +{ + QString &path = (*androidDirCache)[QStringLiteral("EXT_ROOT")]; + if (!path.isEmpty()) + return path; + + QJniObject file = QJniObject::callStaticMethod("android/os/Environment", + "getExternalStorageDirectory"); + if (!file.isValid()) + return QString(); + + return (path = getAbsolutePath(file)); +} + +/* + * Locations where applications can place user files shared by all apps (public). + * E.g., /storage/Music + */ +static QString getExternalStoragePublicDirectory(const char *directoryField) +{ + QString &path = (*androidDirCache)[QLatin1String(directoryField)]; + if (!path.isEmpty()) + return path; + + QJniObject dirField = QJniObject::getStaticField("android/os/Environment", + directoryField); + if (!dirField.isValid()) + return QString(); + + QJniObject file = QJniObject::callStaticMethod("android/os/Environment", + "getExternalStoragePublicDirectory", + dirField.object()); + if (!file.isValid()) + return QString(); + + return (path = getAbsolutePath(file)); +} + /* * Locations where applications can place persistent files it owns. * E.g., /storage/org.app/Music @@ -129,25 +171,35 @@ static QString getFilesDir() return (path = getAbsolutePath(file)); } +static QString getSdkBasedExternalDir(const char *directoryField = nullptr) +{ + return (QNativeInterface::QAndroidApplication::sdkVersion() >= 30) + ? getExternalFilesDir(directoryField) + : getExternalStoragePublicDirectory(directoryField); +} + QString QStandardPaths::writableLocation(StandardLocation type) { switch (type) { case QStandardPaths::MusicLocation: - return getExternalFilesDir("DIRECTORY_MUSIC"); + return getSdkBasedExternalDir("DIRECTORY_MUSIC"); case QStandardPaths::MoviesLocation: - return getExternalFilesDir("DIRECTORY_MOVIES"); + return getSdkBasedExternalDir("DIRECTORY_MOVIES"); case QStandardPaths::PicturesLocation: - return getExternalFilesDir("DIRECTORY_PICTURES"); + return getSdkBasedExternalDir("DIRECTORY_PICTURES"); case QStandardPaths::DocumentsLocation: - return getExternalFilesDir("DIRECTORY_DOCUMENTS"); + return getSdkBasedExternalDir("DIRECTORY_DOCUMENTS"); case QStandardPaths::DownloadLocation: - return getExternalFilesDir("DIRECTORY_DOWNLOADS"); + return getSdkBasedExternalDir("DIRECTORY_DOWNLOADS"); case QStandardPaths::GenericConfigLocation: case QStandardPaths::ConfigLocation: case QStandardPaths::AppConfigLocation: return getFilesDir() + testDir() + "/settings"_L1; case QStandardPaths::GenericDataLocation: - return getExternalFilesDir() + testDir(); + { + return QAndroidApplication::sdkVersion() >= 30 ? + getExternalFilesDir() + testDir() : getExternalStorageDirectory() + testDir(); + } case QStandardPaths::AppDataLocation: case QStandardPaths::AppLocalDataLocation: return getFilesDir() + testDir(); @@ -175,8 +227,14 @@ QStringList QStandardPaths::standardLocations(StandardLocation type) QStringList locations; if (type == MusicLocation) { - locations << getExternalFilesDir("DIRECTORY_MUSIC") - << getExternalFilesDir("DIRECTORY_PODCASTS") + locations << getExternalFilesDir("DIRECTORY_MUSIC"); + // Place the public dirs before the app own dirs + if (QNativeInterface::QAndroidApplication::sdkVersion() < 30) { + locations << getExternalStoragePublicDirectory("DIRECTORY_PODCASTS") + << getExternalStoragePublicDirectory("DIRECTORY_NOTIFICATIONS") + << getExternalStoragePublicDirectory("DIRECTORY_ALARMS"); + } + locations << getExternalFilesDir("DIRECTORY_PODCASTS") << getExternalFilesDir("DIRECTORY_NOTIFICATIONS") << getExternalFilesDir("DIRECTORY_ALARMS"); } else if (type == MoviesLocation) {