From 888de8bcf83926ba3adec7f2c500fae0c0cd129f Mon Sep 17 00:00:00 2001 From: Assam Boudjelthia Date: Sat, 25 Feb 2023 17:37:43 +0200 Subject: 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 CI Bot --- src/corelib/io/qstandardpaths.cpp | 17 ++++--- src/corelib/io/qstandardpaths_android.cpp | 79 +++++++++++++++++++++++++++---- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/corelib/io/qstandardpaths.cpp b/src/corelib/io/qstandardpaths.cpp index ec8e6899e4..a20c50e096 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 efc3661e23..a911087c9c 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" @@ -12,6 +12,9 @@ QT_BEGIN_NAMESPACE +Q_DECLARE_JNI_CLASS(Environment, "android/os/Environment"); +Q_DECLARE_JNI_TYPE(File, "Ljava/io/File;"); + using namespace QNativeInterface; using namespace Qt::StringLiterals; @@ -33,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 @@ -132,25 +177,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(); @@ -178,8 +233,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) { -- cgit v1.2.1