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
qandroidplatformservices.cpp
Go to the documentation of this file.
1// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
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:significant reason:default
5
7
8#if QT_CONFIG(desktopservices)
9#include <QDebug>
10#include <QDesktopServices>
11#include <QFile>
12#include <QMimeDatabase>
13#include <QtCore/QJniObject>
14#include <QtCore/qcoreapplication.h>
15#include <QtCore/qscopedvaluerollback.h>
16
17#include <algorithm>
18#include <utility>
19#endif
20
22
23using namespace QtJniTypes;
24using namespace Qt::StringLiterals;
25
26#if QT_CONFIG(desktopservices)
27static constexpr auto s_defaultScheme = "file"_L1;
28static constexpr auto s_defaultProvider = "qtprovider"_L1;
29#endif
30
32{
33#if QT_CONFIG(desktopservices)
34 m_actionView = QJniObject::getStaticObjectField("android/content/Intent", "ACTION_VIEW",
35 "Ljava/lang/String;")
36 .toString();
37
38 QtAndroidPrivate::registerNewIntentListener(this);
39
40 // Qt applications without Activity contexts cannot retrieve intents from the Activity.
41 if (QNativeInterface::QAndroidApplication::isActivityContext()) {
42 QMetaObject::invokeMethod(
43 this,
44 [this] {
45 QJniObject context = QJniObject(QtAndroidPrivate::context());
46 QJniObject intent =
47 context.callObjectMethod("getIntent", "()Landroid/content/Intent;");
48 handleNewIntent(nullptr, intent.object());
49 },
50 Qt::QueuedConnection);
51 }
52#endif // QT_CONFIG(desktopservices)
53}
54
56{
57 return QByteArray("Android");
58}
59
60Q_DECLARE_JNI_CLASS(FileProvider, "androidx/core/content/FileProvider");
61Q_DECLARE_JNI_CLASS(PackageManager, "android/content/pm/PackageManager");
62Q_DECLARE_JNI_CLASS(PackageInfo, "android/content/pm/PackageInfo");
63Q_DECLARE_JNI_CLASS(ProviderInfo, "android/content/pm/ProviderInfo");
64
65#if QT_CONFIG(desktopservices)
66bool QAndroidPlatformServices::openUrl(const QUrl &theUrl)
67{
68 QUrl url(theUrl);
69
70 // avoid recursing back into self
71 if (url == m_handlingUrl)
72 return false;
73
74 // a real URL including the scheme is needed, else the Intent can not be started
75 if (url.scheme().isEmpty())
76 url.setScheme(s_defaultScheme);
77
78 const int sdkVersion = QNativeInterface::QAndroidApplication::sdkVersion();
79 if (url.scheme() != s_defaultScheme || sdkVersion < 24 )
80 return openURL(url);
81 return openUrlWithFileProvider(url);
82}
83
84QString QAndroidPlatformServices::getMimeOfUrl(const QUrl &url) const
85{
86 QString mime;
87 if (url.scheme() == s_defaultScheme)
88 mime = QMimeDatabase().mimeTypeForUrl(url).name();
89 return mime;
90}
91
92bool QAndroidPlatformServices::openURL(const QUrl &url) const
93{
94 return openURL(url.toString(QUrl::FullyEncoded));
95}
96
97bool QAndroidPlatformServices::openURL(const QString &url) const
98{
99 return QJniObject::callStaticMethod<jboolean>(
100 QtAndroid::applicationClass(), "openURL",
101 QNativeInterface::QAndroidApplication::context(),
102 url,
103 getMimeOfUrl(url));
104}
105
106// Qt's provider is the fallback, so an application's own is tried first.
107static QStringList reorderApplicationProvidersFirst(QStringList authorities)
108{
109 std::stable_partition(authorities.begin(), authorities.end(), [](const QString &authority) {
110 return !authority.endsWith(s_defaultProvider);
111 });
112 return authorities;
113}
114
115bool QAndroidPlatformServices::openUrlWithFileProvider(const QUrl &url)
116{
117 const QJniObject context = QNativeInterface::QAndroidApplication::context();
118 const QStringList authorities =
119 reorderApplicationProvidersFirst(getFileProviderAuthorities(context));
120 if (authorities.isEmpty())
121 return false;
122
123 // Only getUriForFile() knows which provider covers a file, so try each.
124 for (const QString &authority : authorities) {
125 const QJniObject uri = fileProviderUri(url, authority);
126 if (uri.isValid())
127 return openURL(uri.toString());
128 }
129 return false;
130}
131
132QJniObject QAndroidPlatformServices::fileProviderUri(const QUrl &url,
133 const QString &authority) const
134{
135 const auto urlPath = QJniObject::fromString(url.path());
136 const auto urlFile = QJniObject(Traits<File>::className(),
137 urlPath.object<jstring>());
138 // Throws if no root covers the file, surfacing here as an invalid object.
139 return QJniObject::callStaticMethod<Uri>(
140 Traits<FileProvider>::className(), "getUriForFile",
141 QNativeInterface::QAndroidApplication::context(), authority,
142 urlFile.object<File>());
143}
144
145QStringList QAndroidPlatformServices::getFileProviderAuthorities(const QJniObject &context) const
146{
147 QStringList authorityList;
148
149 const auto packageManager = context.callMethod<PackageManager>("getPackageManager");
150 const auto packageName = context.callMethod<QString>("getPackageName");
151 const auto packageInfo = packageManager.callMethod<PackageInfo>("getPackageInfo",
152 packageName,
153 8 /* PackageManager.GET_PROVIDERS */);
154 const auto providersArray = packageInfo.getField<ProviderInfo[]>("providers");
155
156 if (providersArray.isValid()) {
157 const auto className = Traits<FileProvider>::className();
158 for (const auto &fileProvider : providersArray) {
159 auto providerName = fileProvider.getField<QString>("name");
160 if (providerName.replace(".", "/").contains(className.data())) {
161 const auto authority = fileProvider.getField<QString>("authority");
162 if (!authority.isEmpty())
163 authorityList << authority;
164 }
165 }
166 }
167 if (authorityList.isEmpty())
168 qWarning() << "No file provider found in the AndroidManifest.xml.";
169
170 return authorityList;
171}
172
173bool QAndroidPlatformServices::openDocument(const QUrl &url)
174{
175 return openUrl(url);
176}
177
178bool QAndroidPlatformServices::handleNewIntent(JNIEnv *env, jobject intent)
179{
180 Q_UNUSED(env);
181
182 const QJniObject jniIntent(intent);
183
184 const QString action = jniIntent.callObjectMethod<jstring>("getAction").toString();
185 if (action != m_actionView)
186 return false;
187
188 const QString url = jniIntent.callObjectMethod<jstring>("getDataString").toString();
189 QScopedValueRollback<QUrl> rollback(m_handlingUrl, url);
190 return QDesktopServices::openUrl(url);
191}
192#endif // QT_CONFIG(desktopservices)
193
194QT_END_NAMESPACE
QByteArray desktopEnvironment() const override
QPlatformServices::desktopEnvironment returns the active desktop environment.
Combined button and popup list for selecting options.
Q_DECLARE_JNI_CLASS(MotionEvent, "android/view/MotionEvent")