diff options
author | Assam Boudjelthia <assam.boudjelthia@qt.io> | 2022-08-24 21:06:45 +0300 |
---|---|---|
committer | Assam Boudjelthia <assam.boudjelthia@qt.io> | 2023-02-24 17:45:10 +0200 |
commit | 738c48244bb7fe2a29e5f8f537a6f6c2ebb33008 (patch) | |
tree | 40e170713bba44d9246de5f6f0274e32b25ea9b6 | |
parent | b8d51001f7d1c40cced642b4a1d990c90244706f (diff) |
Android: use FileProvider with QDesktopServices::openUrl()
Allow openUrl() to use FileProvider for opening files that are located
under app scoped paths and that use a file scheme for Android sdk 24 or
above.
[ChangeLog][Core][Android] Add FileProvider support for
QDesktopServices::openUrl().
[ChangeLog][Core][Android] Add AndroidX dependency to Gradle builds
by default since it's required by FileProvider.
Fixes: QTBUG-85238
Change-Id: Ia7403f74f2a8fd4886f74dba72e42b318ef5d079
Reviewed-by: Konstantin Ritt <ritt.ks@gmail.com>
Reviewed-by: Nicholas Bennett <nicholas.bennett@qt.io>
Reviewed-by: Ville Voutilainen <ville.voutilainen@qt.io>
-rw-r--r-- | src/3rdparty/gradle/gradle.properties | 3 | ||||
-rw-r--r-- | src/android/templates/AndroidManifest.xml | 10 | ||||
-rw-r--r-- | src/android/templates/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/android/templates/build.gradle | 1 | ||||
-rw-r--r-- | src/android/templates/res/xml/qtprovider_paths.xml | 4 | ||||
-rw-r--r-- | src/gui/doc/src/external-resources.qdoc | 10 | ||||
-rw-r--r-- | src/gui/util/qdesktopservices.cpp | 7 | ||||
-rw-r--r-- | src/plugins/platforms/android/qandroidplatformservices.cpp | 40 |
8 files changed, 70 insertions, 6 deletions
diff --git a/src/3rdparty/gradle/gradle.properties b/src/3rdparty/gradle/gradle.properties index 263d70238a..8308822585 100644 --- a/src/3rdparty/gradle/gradle.properties +++ b/src/3rdparty/gradle/gradle.properties @@ -12,3 +12,6 @@ org.gradle.parallel=true # build with the same inputs. However, over time, the cache size will # grow. Uncomment the following line to enable it. #org.gradle.caching=true + +# Allow AndroidX usage +android.useAndroidX=true diff --git a/src/android/templates/AndroidManifest.xml b/src/android/templates/AndroidManifest.xml index d7438317b8..8f91863719 100644 --- a/src/android/templates/AndroidManifest.xml +++ b/src/android/templates/AndroidManifest.xml @@ -43,5 +43,15 @@ android:name="android.app.extract_android_style" android:value="minimal" /> </activity> + + <provider + android:name="androidx.core.content.FileProvider" + android:authorities="${applicationId}.qtprovider" + android:exported="false" + android:grantUriPermissions="true"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/qtprovider_paths"/> + </provider> </application> </manifest> diff --git a/src/android/templates/CMakeLists.txt b/src/android/templates/CMakeLists.txt index 0d710f72f4..94f3243c22 100644 --- a/src/android/templates/CMakeLists.txt +++ b/src/android/templates/CMakeLists.txt @@ -14,6 +14,7 @@ add_custom_target(Qt${QtBase_VERSION_MAJOR}AndroidTemplates SOURCES ${template_files} "${CMAKE_CURRENT_SOURCE_DIR}/res/values/libs.xml" + "${CMAKE_CURRENT_SOURCE_DIR}/res/xml/qtprovider_paths.xml" ) qt_path_join(destination ${QT_INSTALL_DIR} ${INSTALL_DATADIR} "src/android/templates") diff --git a/src/android/templates/build.gradle b/src/android/templates/build.gradle index f6c2f3d56d..33867903c2 100644 --- a/src/android/templates/build.gradle +++ b/src/android/templates/build.gradle @@ -18,6 +18,7 @@ apply plugin: 'com.android.application' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) + implementation 'androidx.core:core:1.8.0' } android { diff --git a/src/android/templates/res/xml/qtprovider_paths.xml b/src/android/templates/res/xml/qtprovider_paths.xml new file mode 100644 index 0000000000..ae5b4b6074 --- /dev/null +++ b/src/android/templates/res/xml/qtprovider_paths.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<paths xmlns:android="http://schemas.android.com/apk/res/android"> + <files-path name="files_path" path="/"/> +</paths> diff --git a/src/gui/doc/src/external-resources.qdoc b/src/gui/doc/src/external-resources.qdoc index cea63f59cc..67ba9f3013 100644 --- a/src/gui/doc/src/external-resources.qdoc +++ b/src/gui/doc/src/external-resources.qdoc @@ -46,3 +46,13 @@ \externalpage https://www.lunarg.com/vulkan-sdk/ \title LunarG Vulkan SDK */ + +/*! + \externalpage https://developer.android.com/reference/androidx/core/content/FileProvider + \title Android: FileProvider +*/ + +/*! + \externalpage https://developer.android.com/training/secure-file-sharing/setup-sharing.html + \title Android: Setting up file sharing +*/ diff --git a/src/gui/util/qdesktopservices.cpp b/src/gui/util/qdesktopservices.cpp index ae452885fc..4a12f6db6f 100644 --- a/src/gui/util/qdesktopservices.cpp +++ b/src/gui/util/qdesktopservices.cpp @@ -162,6 +162,13 @@ void QOpenUrlHandlerRegistry::handlerDestroyed(QObject *handler) \snippet code/src_gui_util_qdesktopservices.cpp 3 + \note For Android Nougat (SDK 24) and above, URLs with a \c file scheme + are opened using \l {Android: FileProvider}{FileProvider} which tries to obtain + a shareable \c content scheme URI first. For that reason, Qt for Android defines + a file provider with the authority \c ${applicationId}.qtprovider, with \c applicationId + being the app's package name to avoid name conflicts. For more information, also see + \l {Android: Setting up file sharing}{Setting up file sharing}. + \sa setUrlHandler() */ bool QDesktopServices::openUrl(const QUrl &url) diff --git a/src/plugins/platforms/android/qandroidplatformservices.cpp b/src/plugins/platforms/android/qandroidplatformservices.cpp index 2599fe6db2..5964236420 100644 --- a/src/plugins/platforms/android/qandroidplatformservices.cpp +++ b/src/plugins/platforms/android/qandroidplatformservices.cpp @@ -35,6 +35,11 @@ QAndroidPlatformServices::QAndroidPlatformServices() Qt::QueuedConnection); } +Q_DECLARE_JNI_TYPE(UriType, "Landroid/net/Uri;") +Q_DECLARE_JNI_TYPE(FileType, "Ljava/io/File;") +Q_DECLARE_JNI_CLASS(File, "java/io/File") +Q_DECLARE_JNI_CLASS(FileProvider, "androidx/core/content/FileProvider"); + bool QAndroidPlatformServices::openUrl(const QUrl &theUrl) { QString mime; @@ -55,13 +60,36 @@ bool QAndroidPlatformServices::openUrl(const QUrl &theUrl) if (url.scheme() == fileScheme) mime = QMimeDatabase().mimeTypeForUrl(url).name(); + const QJniObject mimeString = QJniObject::fromString(mime); + using namespace QNativeInterface; - QJniObject urlString = QJniObject::fromString(url.toString()); - QJniObject mimeString = QJniObject::fromString(mime); - return QJniObject::callStaticMethod<jboolean>( - QtAndroid::applicationClass(), "openURL", - "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)Z", - QAndroidApplication::context(), urlString.object(), mimeString.object()); + + auto openUrl = [mimeString](const QJniObject &url) { + return QJniObject::callStaticMethod<jboolean>(QtAndroid::applicationClass(), "openURL", + QAndroidApplication::context(), url.object<jstring>(), mimeString.object<jstring>()); + }; + + if (url.scheme() != fileScheme || QNativeInterface::QAndroidApplication::sdkVersion() < 24) + return openUrl(QJniObject::fromString(url.toString())); + + // Use FileProvider for file scheme with sdk >= 24 + const QJniObject context = QAndroidApplication::context(); + const auto appId = context.callMethod<jstring>("getPackageName").toString(); + const auto providerName = QJniObject::fromString(appId + ".qtprovider"_L1); + + const auto urlPath = QJniObject::fromString(url.path()); + const auto urlFile = QJniObject(QtJniTypes::className<QtJniTypes::File>(), + urlPath.object<jstring>()); + + const auto fileProviderUri = QJniObject::callStaticMethod<QtJniTypes::UriType>( + QtJniTypes::className<QtJniTypes::FileProvider>(), "getUriForFile", + QAndroidApplication::context(), providerName.object<jstring>(), + urlFile.object<QtJniTypes::FileType>()); + + if (fileProviderUri.isValid()) + return openUrl(fileProviderUri.callMethod<jstring>("toString")); + + return false; } bool QAndroidPlatformServices::openDocument(const QUrl &url) |