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
146void QAndroidPlatformFileDialogHelper::setMimeTypes()
147{
148 QStringList mimeTypes = options()->mimeTypeFilters();
149 const QStringList nameFilters = options()->nameFilters();
150
151 if (!nameFilters.isEmpty()) {
152 QMimeDatabase db;
153 for (auto filter : nameFilters) {
154 if (!filter.isEmpty()) {
155 for (const QString &filter : nameFilterExtensions(filter))
156 mimeTypes.append(db.mimeTypeForFile(filter, QMimeDatabase::MatchExtension).name());
157 }
158 }
159 }
160
161 const QString initialType = mimeTypes.size() == 1 ? mimeTypes.at(0) : "*/*"_L1;
162 m_intent.callObjectMethod("setType", "(Ljava/lang/String;)Landroid/content/Intent;",
163 QJniObject::fromString(initialType).object());
164
165 if (!mimeTypes.isEmpty()) {
166 const QJniObject extraMimeType = QJniObject::getStaticObjectField(
167 JniIntentClass, "EXTRA_MIME_TYPES", "Ljava/lang/String;");
168
169 const QJniObject mimeTypesArray = QJniObject::callStaticObjectMethod(
170 "org/qtproject/qt/android/QtNative",
171 "getStringArray",
172 "(Ljava/lang/String;)[Ljava/lang/String;",
173 QJniObject::fromString(mimeTypes.join(",")).object());
174
175 m_intent.callObjectMethod(
176 "putExtra", "(Ljava/lang/String;[Ljava/lang/String;)Landroid/content/Intent;",
177 extraMimeType.object(), mimeTypesArray.object());
178 }
179}
180
181QJniObject QAndroidPlatformFileDialogHelper::getFileDialogIntent(const QString &intentType)
182{
183 const QJniObject ACTION_OPEN_DOCUMENT = QJniObject::getStaticObjectField(
184 JniIntentClass, intentType.toLatin1(), "Ljava/lang/String;");
185 return QJniObject(JniIntentClass, "(Ljava/lang/String;)V",
186 ACTION_OPEN_DOCUMENT.object());
187}
188
189bool QAndroidPlatformFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent)
190{
191 Q_UNUSED(windowFlags);
192 Q_UNUSED(windowModality);
193 Q_UNUSED(parent);
194
195 bool isDirDialog = false;
196
197 m_selectedFile.clear();
198
199 if (options()->acceptMode() == QFileDialogOptions::AcceptSave) {
200 m_intent = getFileDialogIntent("ACTION_CREATE_DOCUMENT");
201 const QList<QUrl> selectedFiles = options()->initiallySelectedFiles();
202 if (selectedFiles.size() > 0)
203 setInitialFileName(selectedFiles.first().fileName());
204 } else if (options()->acceptMode() == QFileDialogOptions::AcceptOpen) {
205 switch (options()->fileMode()) {
206 case QFileDialogOptions::FileMode::DirectoryOnly:
207 case QFileDialogOptions::FileMode::Directory:
208 m_intent = getFileDialogIntent("ACTION_OPEN_DOCUMENT_TREE");
209 isDirDialog = true;
210 break;
211 case QFileDialogOptions::FileMode::ExistingFiles:
212 m_intent = getFileDialogIntent("ACTION_OPEN_DOCUMENT");
213 setAllowMultipleSelections(true);
214 break;
215 case QFileDialogOptions::FileMode::AnyFile:
216 case QFileDialogOptions::FileMode::ExistingFile:
217 m_intent = getFileDialogIntent("ACTION_OPEN_DOCUMENT");
218 break;
219 }
220 }
221
222 if (!isDirDialog) {
223 setOpenableCategory();
224 setMimeTypes();
225 }
226
227 setInitialDirectoryUri(m_directory.toString());
228
229 QtAndroidPrivate::registerActivityResultListener(this);
230 m_activity.callMethod<void>("startActivityForResult", "(Landroid/content/Intent;I)V",
231 m_intent.object(), REQUEST_CODE);
232 return true;
233}
234
236{
237 if (m_eventLoop.isRunning())
238 m_eventLoop.exit();
239 QtAndroidPrivate::unregisterActivityResultListener(this);
240}
241
243{
244 m_directory = directory;
245}
246
248{
249 m_eventLoop.exec(QEventLoop::DialogExec);
250}
251}
252
253QT_END_NAMESPACE
bool handleActivityResult(jint requestCode, jint resultCode, jobject data) override
QStringList nameFilterExtensions(const QString nameFilters)