diff options
author | Assam Boudjelthia <assam.boudjelthia@qt.io> | 2022-11-23 14:30:50 +0200 |
---|---|---|
committer | Assam Boudjelthia <assam.boudjelthia@qt.io> | 2022-12-27 14:47:37 +0200 |
commit | 732c6ab2dd3b6c65946b7b6403034fbd37530421 (patch) | |
tree | 1e938d4f9730340135ee786a46f721f26a085bf5 /tests | |
parent | ab1338914a49d93225ce4f5c6eddb0226d281a37 (diff) |
Android: Add facilities to handle more content URIs operations
Use DocumentFile and DocumentsContract to support more operations
on content URIs, such as:
* listing files and subdirectories with usable content uris
* mkdir, rmdir
* creating non-existing files under a tree uri
* remove
And since dealing with content URIs require some level of user
interation, manual tests were added to cover what's been implemented.
Note: parts of the code were from from BogDan Vatra <bogdan@kdab.com>.
Task-number: QTBUG-98974
Task-number: QTBUG-104776
Change-Id: I3d64958ef26d0155210905b65daae2efa3db31c1
Reviewed-by: Ville Voutilainen <ville.voutilainen@qt.io>
(cherry picked from commit e5d591a0d09032d1870e47d1bf59c9069ea0a943)
Diffstat (limited to 'tests')
-rw-r--r-- | tests/manual/CMakeLists.txt | 4 | ||||
-rw-r--r-- | tests/manual/android_content_uri/CMakeLists.txt | 7 | ||||
-rw-r--r-- | tests/manual/android_content_uri/tst_content_uris.cpp | 201 |
3 files changed, 212 insertions, 0 deletions
diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt index f9eb23cd5f..869480f39d 100644 --- a/tests/manual/CMakeLists.txt +++ b/tests/manual/CMakeLists.txt @@ -81,3 +81,7 @@ endif() if(QT_FEATURE_vulkan) add_subdirectory(qvulkaninstance) endif() + +if(ANDROID) + add_subdirectory(android_content_uri) +endif() diff --git a/tests/manual/android_content_uri/CMakeLists.txt b/tests/manual/android_content_uri/CMakeLists.txt new file mode 100644 index 0000000000..a8a815fd94 --- /dev/null +++ b/tests/manual/android_content_uri/CMakeLists.txt @@ -0,0 +1,7 @@ +qt_internal_add_test(tst_content_uris + SOURCES + tst_content_uris.cpp + LIBRARIES + Qt::CorePrivate + Qt::Widgets +) diff --git a/tests/manual/android_content_uri/tst_content_uris.cpp b/tests/manual/android_content_uri/tst_content_uris.cpp new file mode 100644 index 0000000000..1fea742af7 --- /dev/null +++ b/tests/manual/android_content_uri/tst_content_uris.cpp @@ -0,0 +1,201 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QTest> +#include <QDirIterator> +#include <QFileDialog> +#include <QMessageBox> + +class tst_ContentUris: public QObject +{ + Q_OBJECT +private slots: + void dirFacilities(); + void readWriteFile(); + void readWriteNonExistingFile_data(); + void readWriteNonExistingFile(); + void createFileFromDirUrl_data(); + void createFileFromDirUrl(); + void fileOperations(); +}; + +static QStringList listFiles(const QDir &dir, QDirIterator::IteratorFlag flag = {}) +{ + QDirIterator it(dir, flag); + QStringList dirs; + while (it.hasNext()) + dirs << it.next(); + return dirs; +} + +void showInstructionsDialog(const QString &message) +{ + QMessageBox::information(nullptr, QLatin1String("Instructions"), message); +} + +void tst_ContentUris::dirFacilities() +{ + showInstructionsDialog(QLatin1String("Choose a folder with no content/files/subdirs")); + + auto url = QFileDialog::getExistingDirectory(); + QVERIFY(url.startsWith(QLatin1String("content"))); + QDir dir(url); + + QVERIFY(dir.exists()); + QVERIFY(!dir.dirName().isEmpty()); + QVERIFY(listFiles(dir).isEmpty()); + + QVERIFY(dir.mkdir(QLatin1String("Sub"))); + const auto dirList = listFiles(dir); + QVERIFY(dirList.size() == 1); + const QDir subDir = dirList.first(); + + QVERIFY(subDir.dirName() == QLatin1String("Sub")); + QEXPECT_FAIL("", "absolutePath() is returning wrong path, cutting from 'primary' onward", Continue); + qWarning() << "subDir.absolutePath()" << subDir.absolutePath() << dirList.first(); + QVERIFY(subDir.absolutePath() == dirList.first()); + QVERIFY(subDir.path() == dirList.first()); + + QVERIFY(listFiles(dir, QDirIterator::Subdirectories).size() == 1); + QVERIFY(dir.mkdir(QLatin1String("Sub"))); // Create an existing dir + QVERIFY(dir.rmdir(QLatin1String("Sub"))); + + QVERIFY(dir.mkpath(QLatin1String("Sub/Sub2/Sub3"))); + QVERIFY(listFiles(dir).size() == 1); + QVERIFY(listFiles(dir, QDirIterator::Subdirectories).size() == 3); + QVERIFY(dir.mkpath(QLatin1String("Sub/Sub2/Sub3"))); // Create an existing dir hierarchy + QVERIFY(dir.rmdir(QLatin1String("Sub"))); + +} + +void tst_ContentUris::readWriteFile() +{ + const QByteArray content = "Written to file"; + const QString fileName = QLatin1String("new_file.txt"); + + { + showInstructionsDialog(QLatin1String("Choose a name for new file to create")); + + auto url = QFileDialog::getSaveFileName(nullptr, tr("Save File"), fileName); + QFile file(url); + QVERIFY(file.exists()); + QVERIFY(file.size() == 0); + QVERIFY(file.fileName() == url); + QVERIFY(QFileInfo(url).baseName() == fileName); + + QVERIFY(file.open(QFile::WriteOnly)); + QVERIFY(file.isOpen()); + QVERIFY(file.isWritable()); + QVERIFY(file.fileTime(QFileDevice::FileModificationTime) != QDateTime()); + QVERIFY(file.write(content) > 0); + QVERIFY(file.size() == content.size()); + file.close(); + + // NOTE: The native file cursor is not returning an updated time or it takes long + // for it to get updated, for now just check that we actually received a valid QDateTime + QVERIFY(file.fileTime(QFileDevice::FileModificationTime) != QDateTime()); + } + + { + showInstructionsDialog(QLatin1String("Choose the file that was created")); + + auto url = QFileDialog::getOpenFileName(nullptr, tr("Open File"), fileName); + QFile file(url); + QVERIFY(file.exists()); + + QVERIFY(file.open(QFile::ReadOnly)); + QVERIFY(file.isOpen()); + QVERIFY(file.isReadable()); + QVERIFY(file.readAll() == content); + + QVERIFY(file.remove()); + } +} + +void tst_ContentUris::readWriteNonExistingFile_data() +{ + QTest::addColumn<QString>("path"); + + const QString fileName = "non-existing-file.txt"; + const QString uriSchemeAuthority = "content://com.android.externalstorage.documents"; + const QString id = "primary%3APictures"; + const QString encSlash = QUrl::toPercentEncoding(QLatin1String("/")); + + const QString docSlash = uriSchemeAuthority + QLatin1String("/document/") + id + QLatin1String("/") + fileName; + const QString docEncodedSlash = uriSchemeAuthority + QLatin1String("/document/") + id + encSlash + fileName; + + QTest::newRow("document_with_slash") << docSlash; + QTest::newRow("document_with_encoded_slash") << docEncodedSlash; +} + +void tst_ContentUris::readWriteNonExistingFile() +{ + QFETCH(QString, path); + + QFile file(path); + QVERIFY(!file.exists()); + QVERIFY(file.size() == 0); + + QVERIFY(!file.open(QFile::WriteOnly)); + QVERIFY(!file.isOpen()); + QVERIFY(!file.isWritable()); +} + +void tst_ContentUris::createFileFromDirUrl_data() +{ + QTest::addColumn<QString>("path"); + + showInstructionsDialog("Choose a folder with no content/files/subdirs"); + + const QString treeUrl = QFileDialog::getExistingDirectory(); + const QString fileName = "text.txt"; + const QString treeSlash = treeUrl + QLatin1String("/") + fileName; + QTest::newRow("tree_with_slash") << treeSlash; + + // TODO: This is not handled at the moment + // const QString encSlash = QUrl::toPercentEncoding("/"_L1); + // const QString treeEncodedSlash = treeUrl + encSlash + fileName; + // QTest::newRow("tree_with_encoded_slash") << treeEncodedSlash; +} + +void tst_ContentUris::createFileFromDirUrl() +{ + QFETCH(QString, path); + + const QByteArray content = "Written to file"; + + QFile file(path); + QVERIFY(!file.exists()); + QVERIFY(file.size() == 0); + + QVERIFY(file.open(QFile::WriteOnly)); + QVERIFY(file.isOpen()); + QVERIFY(file.isWritable()); + QVERIFY(file.exists()); + QVERIFY(file.write(content)); + QVERIFY(file.size() == content.size()); + file.close(); + + QVERIFY(file.open(QFile::ReadOnly)); + QVERIFY(file.isOpen()); + QVERIFY(file.isReadable()); + QVERIFY(file.readAll() == content); + + QVERIFY(file.remove()); +} + +void tst_ContentUris::fileOperations() +{ + showInstructionsDialog("Choose a name for new file to create"); + + const QString fileName = "new_file.txt"; + auto url = QFileDialog::getSaveFileName(nullptr, tr("Save File"), fileName); + QFile file(url); + QVERIFY(file.exists()); + + QVERIFY(file.remove()); + QVERIFY(!file.exists()); +} + +QTEST_MAIN(tst_ContentUris) +#include "tst_content_uris.moc" |