diff options
author | Assam Boudjelthia <assam.boudjelthia@qt.io> | 2022-11-22 14:06:33 +0200 |
---|---|---|
committer | Assam Boudjelthia <assam.boudjelthia@qt.io> | 2022-12-27 14:47:41 +0200 |
commit | fc10f05c5757d5be85923dc1278ff87ace6aa4d6 (patch) | |
tree | b182db0c43f066b350c526ec2558a500ee757f49 | |
parent | 732c6ab2dd3b6c65946b7b6403034fbd37530421 (diff) |
Android: handle rename() operation with content uris
Allow renaming content uris if the destination is provided
as a direct fileName (i.e. not full content scheme path),
and if the destination has the same trailing path (or parent)
which means a rename in the same folder structure.
Task-number: QTBUG-98974
Change-Id: Ibc4973366807dd5284c19912ab04ff90f2a573cb
Reviewed-by: Ville Voutilainen <ville.voutilainen@qt.io>
(cherry picked from commit c1fa5d602c541b06e3e2fc2d02f5d62060c84504)
3 files changed, 82 insertions, 0 deletions
diff --git a/src/plugins/platforms/android/androidcontentfileengine.cpp b/src/plugins/platforms/android/androidcontentfileengine.cpp index d5ea42dffe..406753ac74 100644 --- a/src/plugins/platforms/android/androidcontentfileengine.cpp +++ b/src/plugins/platforms/android/androidcontentfileengine.cpp @@ -145,6 +145,15 @@ bool AndroidContentFileEngine::remove() return m_documentFile->remove(); } +bool AndroidContentFileEngine::rename(const QString &newName) +{ + if (m_documentFile->rename(newName)) { + m_initialFile = m_documentFile->uri().toString(); + return true; + } + return false; +} + bool AndroidContentFileEngine::mkdir(const QString &dirName, bool createParentDirectories) const { QString tmp = dirName; @@ -464,6 +473,7 @@ const QLatin1String COLUMN_SIZE("_size"); constexpr int FLAG_DIR_SUPPORTS_CREATE = 0x00000008; constexpr int FLAG_SUPPORTS_DELETE = 0x00000004; +constexpr int FLAG_SUPPORTS_RENAME = 0x00000040; constexpr int FLAG_SUPPORTS_WRITE = 0x00000002; constexpr int FLAG_VIRTUAL_DOCUMENT = 0x00000200; @@ -547,6 +557,19 @@ bool deleteDocument(const QJniObject &documentUri) documentUri.object()); } +QJniObject renameDocument(const QJniObject &documentUri, const QString &displayName) +{ + const int flags = Cursor::queryColumn(documentUri, Document::COLUMN_FLAGS).toInt(); + if (!(flags & Document::FLAG_SUPPORTS_RENAME)) + return {}; + + return QJniObject::callStaticObjectMethod("android/provider/DocumentsContract", + "renameDocument", + "(Landroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;", + contentResolverInstance().object(), + documentUri.object(), + QJniObject::fromString(displayName).object()); +} } // End DocumentsContract namespace // Start of DocumentFile @@ -766,4 +789,37 @@ std::vector<DocumentFilePtr> DocumentFile::listFiles() return res; } +bool DocumentFile::rename(const QString &newName) +{ + QJniObject uri; + if (newName.startsWith(QLatin1String("content://"))) { + auto lastSeparatorIndex = [](const QString &file) { + int posDecoded = file.lastIndexOf(QLatin1Char('/')); + int posEncoded = file.lastIndexOf(QUrl::toPercentEncoding(QLatin1String("/"))); + return posEncoded > posDecoded ? posEncoded : posDecoded; + }; + + // first try to see if the new file is under the same tree and thus used rename only + const QString parent = m_uri.toString().left(lastSeparatorIndex(m_uri.toString())); + if (newName.contains(parent)) { + QString displayName = newName.mid(lastSeparatorIndex(newName)); + if (displayName.startsWith(QLatin1Char('/'))) + displayName.remove(0, 1); + else if (displayName.startsWith(QUrl::toPercentEncoding(QLatin1String("/")))) + displayName.remove(0, 3); + + uri = renameDocument(m_uri, displayName); + } + } else { + uri = renameDocument(m_uri, newName); + } + + if (uri.isValid()) { + m_uri = uri; + return true; + } + + return false; +} + // End of DocumentFile diff --git a/src/plugins/platforms/android/androidcontentfileengine.h b/src/plugins/platforms/android/androidcontentfileengine.h index da939ee501..566aa1a459 100644 --- a/src/plugins/platforms/android/androidcontentfileengine.h +++ b/src/plugins/platforms/android/androidcontentfileengine.h @@ -55,6 +55,7 @@ public: bool close() override; qint64 size() const override; bool remove() override; + bool rename(const QString &newName) override; bool mkdir(const QString &dirName, bool createParentDirectories) const override; bool rmdir(const QString &dirName, bool recurseParentDirectories) const override; QByteArray id() const override; @@ -126,6 +127,7 @@ public: bool remove(); bool exists() const; std::vector<DocumentFilePtr> listFiles(); + bool rename(const QString &newName); protected: DocumentFile(const QJniObject &uri, const std::shared_ptr<DocumentFile> &parent); diff --git a/tests/manual/android_content_uri/tst_content_uris.cpp b/tests/manual/android_content_uri/tst_content_uris.cpp index 1fea742af7..e0fb10eb98 100644 --- a/tests/manual/android_content_uri/tst_content_uris.cpp +++ b/tests/manual/android_content_uri/tst_content_uris.cpp @@ -193,6 +193,30 @@ void tst_ContentUris::fileOperations() QFile file(url); QVERIFY(file.exists()); + // Rename + const QString renamedFileName = "renamed_new_file.txt"; + QVERIFY(file.rename(renamedFileName)); + const auto renamedUrl = url.replace(fileName, renamedFileName); + QVERIFY(file.fileName() == renamedUrl); + + // NOTE: The uri doesn't seem to stay usable after a rename and it needs to get + // permission again via the SAF picker. + showInstructionsDialog("Choose the file that was renamed"); + QFileDialog::getOpenFileName(nullptr, tr("Open File")); + QVERIFY(file.exists()); + + // rename now with full content uri + const auto secondRenamedUrl = url.replace(renamedFileName, "second_nenamed_file.txt"); + QVERIFY(file.rename(secondRenamedUrl)); + QVERIFY(file.fileName() == secondRenamedUrl); + + // NOTE: The uri doesn't seem to stay usable after a rename and it needs to get + // permission again via the SAF picker. + showInstructionsDialog("Choose the file that was renamed"); + QFileDialog::getOpenFileName(nullptr, tr("Open File")); + QVERIFY(file.exists()); + + // Remove QVERIFY(file.remove()); QVERIFY(!file.exists()); } |