Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qandroidplatformfiledialoghelper.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 Klaralvdalens Datakonsult AB (KDAB)
2// Copyright (C) 2021 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:critical reason:data-parser
5
7
8#include <androidjnimain.h>
9#include <QtCore/QJniObject>
10
11#include <QMimeDatabase>
12#include <QMimeType>
13#include <QRegularExpression>
14#include <QUrl>
15
17
18using namespace Qt::StringLiterals;
19
21
22#define RESULT_OK -1
23#define REQUEST_CODE 1305 // Arbitrary
24
25const char JniIntentClass[] = "android/content/Intent";
26
27QAndroidPlatformFileDialogHelper::QAndroidPlatformFileDialogHelper()
28 : QPlatformFileDialogHelper(),
29 m_activity(QtAndroidPrivate::activity())
30{
31}
32
33bool QAndroidPlatformFileDialogHelper::handleActivityResult(jint requestCode, jint resultCode, jobject data)
34{
35 if (requestCode != REQUEST_CODE)
36 return false;
37
38 if (resultCode != RESULT_OK) {
39 Q_EMIT reject();
40 return true;
41 }
42
43 const QJniObject intent = QJniObject::fromLocalRef(data);
44
45 const QJniObject uri = intent.callObjectMethod("getData", "()Landroid/net/Uri;");
46 if (uri.isValid()) {
47 takePersistableUriPermission(uri);
48 m_selectedFile.append(QUrl(uri.toString()));
49 Q_EMIT fileSelected(m_selectedFile.constFirst());
50 Q_EMIT currentChanged(m_selectedFile.constFirst());
51 Q_EMIT accept();
52
53 return true;
54 }
55
56 const QJniObject uriClipData =
57 intent.callObjectMethod("getClipData", "()Landroid/content/ClipData;");
58 if (uriClipData.isValid()) {
59 const int size = uriClipData.callMethod<jint>("getItemCount");
60 for (int i = 0; i < size; ++i) {
61 QJniObject item = uriClipData.callObjectMethod(
62 "getItemAt", "(I)Landroid/content/ClipData$Item;", i);
63
64 QJniObject itemUri = item.callObjectMethod("getUri", "()Landroid/net/Uri;");
65 takePersistableUriPermission(itemUri);
66 m_selectedFile.append(itemUri.toString());
67 }
68 Q_EMIT filesSelected(m_selectedFile);
69 Q_EMIT currentChanged(m_selectedFile.constFirst());
70 Q_EMIT accept();
71 }
72
73 return true;
74}
75
76void QAndroidPlatformFileDialogHelper::takePersistableUriPermission(const QJniObject &uri)
77{
78 int modeFlags = QJniObject::getStaticField<jint>(
79 JniIntentClass, "FLAG_GRANT_READ_URI_PERMISSION");
80
81 if (options()->acceptMode() == QFileDialogOptions::AcceptSave) {
82 modeFlags |= QJniObject::getStaticField<jint>(
83 JniIntentClass, "FLAG_GRANT_WRITE_URI_PERMISSION");
84 }
85
86 QJniObject contentResolver = m_activity.callObjectMethod(
87 "getContentResolver", "()Landroid/content/ContentResolver;");
88 contentResolver.callMethod<void>("takePersistableUriPermission", "(Landroid/net/Uri;I)V",
89 uri.object(), modeFlags);
90}
91
92void QAndroidPlatformFileDialogHelper::setInitialFileName(const QString &title)
93{
94 const QJniObject extraTitle = QJniObject::getStaticObjectField(
95 JniIntentClass, "EXTRA_TITLE", "Ljava/lang/String;");
96 m_intent.callObjectMethod("putExtra",
97 "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;",
98 extraTitle.object(), QJniObject::fromString(title).object());
99}
100
101void QAndroidPlatformFileDialogHelper::setInitialDirectoryUri(const QString &directory)
102{
103 if (directory.isEmpty())
104 return;
105
106 if (QNativeInterface::QAndroidApplication::sdkVersion() < 26)
107 return;
108
109 const auto extraInitialUri = QJniObject::getStaticObjectField(
110 "android/provider/DocumentsContract", "EXTRA_INITIAL_URI", "Ljava/lang/String;");
111 m_intent.callObjectMethod("putExtra",
112 "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;",
113 extraInitialUri.object(),
114 QJniObject::fromString(directory).object());
115}
116
117void QAndroidPlatformFileDialogHelper::setOpenableCategory()
118{
119 const QJniObject CATEGORY_OPENABLE = QJniObject::getStaticObjectField(
120 JniIntentClass, "CATEGORY_OPENABLE", "Ljava/lang/String;");
121 m_intent.callObjectMethod("addCategory", "(Ljava/lang/String;)Landroid/content/Intent;",
122 CATEGORY_OPENABLE.object());
123}
124
125void QAndroidPlatformFileDialogHelper::setAllowMultipleSelections(bool allowMultiple)
126{
127 const QJniObject allowMultipleSelections = QJniObject::getStaticObjectField(
128 JniIntentClass, "EXTRA_ALLOW_MULTIPLE", "Ljava/lang/String;");
129 m_intent.callObjectMethod("putExtra", "(Ljava/lang/String;Z)Landroid/content/Intent;",
130 allowMultipleSelections.object(), allowMultiple);
131}
132
133QStringList nameFilterExtensions(const QString nameFilters)
134{
135 QStringList ret;
136#if QT_CONFIG(regularexpression)
137 QRegularExpression re("(\\*\\.[a-z0-9 .]+)");
138 QRegularExpressionMatchIterator i = re.globalMatch(nameFilters);
139 while (i.hasNext())
140 ret << i.next().captured(1).trimmed();
141#endif // QT_CONFIG(regularexpression)
142 ret.removeAll("*");
143 return ret;
144}
145
146static QStringList mimeTypeNames(const QMimeType &type)
147{
148 return QStringList(type.name()) + type.aliases();
149}
150
151void QAndroidPlatformFileDialogHelper::setMimeTypes()
152{
153 QMimeDatabase db;
154 const QStringList mimeTypeFilters = options()->mimeTypeFilters();
155 const QStringList nameFilters = options()->nameFilters();
156 QStringList mimeTypes;
157 mimeTypes.reserve(mimeTypeFilters.size() + nameFilters.size());
158 for (const QString &filter : mimeTypeFilters) {
159 const QMimeType type = db.mimeTypeForName(filter);
160 if (type.isValid())
161 mimeTypes.append(mimeTypeNames(type));
162 else
163 mimeTypes.append(filter);
164 }
165
166 if (!nameFilters.isEmpty()) {
167 for (auto filter : nameFilters) {
168 if (!filter.isEmpty()) {
169 for (const QString &filter : nameFilterExtensions(filter)) {
170 const QMimeType type =
171 db.mimeTypeForFile(filter, QMimeDatabase::MatchExtension);
172 mimeTypes.append(mimeTypeNames(type));
173 }
174 }
175 }
176 }
177 mimeTypes.removeDuplicates();
178
179 const QString initialType = mimeTypes.size() == 1 ? mimeTypes.at(0) : "*/*"_L1;
180 m_intent.callObjectMethod("setType", "(Ljava/lang/String;)Landroid/content/Intent;",
181 QJniObject::fromString(initialType).object());
182
183 if (!mimeTypes.isEmpty()) {
184 const QJniObject extraMimeType = QJniObject::getStaticObjectField(
185 JniIntentClass, "EXTRA_MIME_TYPES", "Ljava/lang/String;");
186
187 const QJniObject mimeTypesArray = QJniObject::callStaticObjectMethod(
188 "org/qtproject/qt/android/QtNative",
189 "getStringArray",
190 "(Ljava/lang/String;)[Ljava/lang/String;",
191 QJniObject::fromString(mimeTypes.join(",")).object());
192
193 m_intent.callObjectMethod(
194 "putExtra", "(Ljava/lang/String;[Ljava/lang/String;)Landroid/content/Intent;",
195 extraMimeType.object(), mimeTypesArray.object());
196 }
197}
198
199QJniObject QAndroidPlatformFileDialogHelper::getFileDialogIntent(const QString &intentType)
200{
201 const QJniObject ACTION_OPEN_DOCUMENT = QJniObject::getStaticObjectField(
202 JniIntentClass, intentType.toLatin1(), "Ljava/lang/String;");
203 return QJniObject(JniIntentClass, "(Ljava/lang/String;)V",
204 ACTION_OPEN_DOCUMENT.object());
205}
206
207bool QAndroidPlatformFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent)
208{
209 Q_UNUSED(windowFlags);
210 Q_UNUSED(windowModality);
211 Q_UNUSED(parent);
212
213 bool isDirDialog = false;
214
215 m_selectedFile.clear();
216
217 if (options()->acceptMode() == QFileDialogOptions::AcceptSave) {
218 m_intent = getFileDialogIntent("ACTION_CREATE_DOCUMENT");
219 const QList<QUrl> selectedFiles = options()->initiallySelectedFiles();
220 if (selectedFiles.size() > 0)
221 setInitialFileName(selectedFiles.first().fileName());
222 } else if (options()->acceptMode() == QFileDialogOptions::AcceptOpen) {
223 switch (options()->fileMode()) {
224 case QFileDialogOptions::FileMode::DirectoryOnly:
225 case QFileDialogOptions::FileMode::Directory:
226 m_intent = getFileDialogIntent("ACTION_OPEN_DOCUMENT_TREE");
227 isDirDialog = true;
228 break;
229 case QFileDialogOptions::FileMode::ExistingFiles:
230 m_intent = getFileDialogIntent("ACTION_OPEN_DOCUMENT");
231 setAllowMultipleSelections(true);
232 break;
233 case QFileDialogOptions::FileMode::AnyFile:
234 case QFileDialogOptions::FileMode::ExistingFile:
235 m_intent = getFileDialogIntent("ACTION_OPEN_DOCUMENT");
236 break;
237 }
238 }
239
240 if (!isDirDialog) {
241 setOpenableCategory();
242 setMimeTypes();
243 }
244
245 setInitialDirectoryUri(m_directory.toString());
246
247 QtAndroidPrivate::registerActivityResultListener(this);
248 m_activity.callMethod<void>("startActivityForResult", "(Landroid/content/Intent;I)V",
249 m_intent.object(), REQUEST_CODE);
250 return true;
251}
252
254{
255 if (m_eventLoop.isRunning())
256 m_eventLoop.exit();
257 QtAndroidPrivate::unregisterActivityResultListener(this);
258}
259
261{
262 m_directory = directory;
263}
264
266{
267 m_eventLoop.exec(QEventLoop::DialogExec);
268}
269}
270
271QT_END_NAMESPACE
bool handleActivityResult(jint requestCode, jint resultCode, jobject data) override
Combined button and popup list for selecting options.
static QStringList mimeTypeNames(const QMimeType &type)
QStringList nameFilterExtensions(const QString nameFilters)