diff --git a/src/plugins/platforms/android/qandroidplatformservices.cpp b/src/plugins/platforms/android/qandroidplatformservices.cpp index 34edc64751b..54f274bf426 100644 --- a/src/plugins/platforms/android/qandroidplatformservices.cpp +++ b/src/plugins/platforms/android/qandroidplatformservices.cpp @@ -14,8 +14,12 @@ QT_BEGIN_NAMESPACE +using namespace QtJniTypes; using namespace Qt::StringLiterals; +static constexpr auto s_defaultScheme = "file"_L1; +static constexpr auto s_defaultProvider = "qtprovider"_L1; + QAndroidPlatformServices::QAndroidPlatformServices() { m_actionView = QJniObject::getStaticObjectField("android/content/Intent", "ACTION_VIEW", @@ -39,59 +43,112 @@ QAndroidPlatformServices::QAndroidPlatformServices() } Q_DECLARE_JNI_CLASS(FileProvider, "androidx/core/content/FileProvider"); +Q_DECLARE_JNI_CLASS(PackageManager, "android/content/pm/PackageManager"); +Q_DECLARE_JNI_CLASS(PackageInfo, "android/content/pm/PackageInfo"); +Q_DECLARE_JNI_CLASS(ProviderInfo, "android/content/pm/ProviderInfo"); bool QAndroidPlatformServices::openUrl(const QUrl &theUrl) { - QString mime; QUrl url(theUrl); // avoid recursing back into self if (url == m_handlingUrl) return false; - // if the file is local, we need to pass the MIME type, otherwise Android - // does not start an Intent to view this file - const auto fileScheme = "file"_L1; - // a real URL including the scheme is needed, else the Intent can not be started if (url.scheme().isEmpty()) - url.setScheme(fileScheme); + url.setScheme(s_defaultScheme); - if (url.scheme() == fileScheme) + const int sdkVersion = QNativeInterface::QAndroidApplication::sdkVersion(); + if (url.scheme() != s_defaultScheme || sdkVersion < 24 ) + return openURL(url); + return openUrlWithFileProvider(url); +} + +QString QAndroidPlatformServices::getMimeOfUrl(const QUrl &url) const +{ + QString mime; + if (url.scheme() == s_defaultScheme) mime = QMimeDatabase().mimeTypeForUrl(url).name(); + return mime; +} - const QJniObject mimeString = QJniObject::fromString(mime); +bool QAndroidPlatformServices::openURL(const QUrl &url) const +{ + return QJniObject::callStaticMethod( + QtAndroid::applicationClass(), "openURL", + QNativeInterface::QAndroidApplication::context(), + url.toString(), + getMimeOfUrl(url)); +} - using namespace QNativeInterface; +bool QAndroidPlatformServices::openUrlWithFileProvider(const QUrl &url) +{ + const QJniObject context = QNativeInterface::QAndroidApplication::context(); + auto authorities = getFileProviderAuthorities(context); + if (authorities.isEmpty()) + return false; + return openUrlWithAuthority(url, getAdequateFileproviderAuthority(authorities)); +} - auto openUrl = [mimeString](const QJniObject &url) { - return QJniObject::callStaticMethod(QtAndroid::applicationClass(), "openURL", - QAndroidApplication::context(), url.object(), mimeString.object()); - }; - if (url.scheme() != fileScheme || QNativeInterface::QAndroidApplication::sdkVersion() < 24) - return openUrl(QJniObject::fromString(url.toString())); +QString QAndroidPlatformServices::getAdequateFileproviderAuthority(const QStringList &authorities) const +{ + if (authorities.size() == 1) + return authorities[0]; - // Use FileProvider for file scheme with sdk >= 24 - const QJniObject context = QAndroidApplication::context(); - const auto appId = context.callMethod("getPackageName").toString(); - const auto providerName = QJniObject::fromString(appId + ".qtprovider"_L1); + QString nonQtAuthority; + for (const auto &authority : authorities) { + if (!authority.endsWith(s_defaultProvider, Qt::CaseSensitive)) { + nonQtAuthority = authority; + break; + } + } + return nonQtAuthority; +} +bool QAndroidPlatformServices::openUrlWithAuthority(const QUrl &url, const QString &authority) +{ const auto urlPath = QJniObject::fromString(url.path()); - const auto urlFile = QJniObject(QtJniTypes::Traits::className(), + const auto urlFile = QJniObject(Traits::className(), urlPath.object()); - - const auto fileProviderUri = QJniObject::callStaticMethod( - QtJniTypes::Traits::className(), "getUriForFile", - QAndroidApplication::context(), providerName.object(), - urlFile.object()); - + const auto fileProviderUri = QJniObject::callStaticMethod( + Traits::className(), "getUriForFile", + QNativeInterface::QAndroidApplication::context(), authority, + urlFile.object()); if (fileProviderUri.isValid()) - return openUrl(fileProviderUri.callMethod("toString")); - + return openURL(url); return false; } +QStringList QAndroidPlatformServices::getFileProviderAuthorities(const QJniObject &context) const +{ + QStringList authorityList; + + const auto packageManager = context.callMethod("getPackageManager"); + const auto packageName = context.callMethod("getPackageName"); + const auto packageInfo = packageManager.callMethod("getPackageInfo", + packageName, + 8 /* PackageManager.GET_PROVIDERS */); + const auto providersArray = packageInfo.getField("providers"); + + if (providersArray.isValid()) { + const auto className = Traits::className(); + for (const auto &fileProvider : providersArray) { + auto providerName = fileProvider.getField("name"); + if (providerName.replace(".", "/").contains(className.data())) { + const auto authority = fileProvider.getField("authority"); + if (!authority.isEmpty()) + authorityList << authority; + } + } + } + if (authorityList.isEmpty()) + qWarning() << "No file provider found in the AndroidManifest.xml."; + + return authorityList; +} + bool QAndroidPlatformServices::openDocument(const QUrl &url) { return openUrl(url); diff --git a/src/plugins/platforms/android/qandroidplatformservices.h b/src/plugins/platforms/android/qandroidplatformservices.h index 762d14deeb7..25768ba10eb 100644 --- a/src/plugins/platforms/android/qandroidplatformservices.h +++ b/src/plugins/platforms/android/qandroidplatformservices.h @@ -25,6 +25,15 @@ public: bool handleNewIntent(JNIEnv *env, jobject intent) override; +private: + bool openURL(const QUrl &url) const; + bool openUrlWithFileProvider(const QUrl &url); + bool openUrlWithAuthority(const QUrl &url, const QString &authority); + + QString getMimeOfUrl(const QUrl &url) const; + QStringList getFileProviderAuthorities(const QJniObject &context) const; + QString getAdequateFileproviderAuthority(const QStringList &authorities) const; + private: QUrl m_handlingUrl; QString m_actionView;