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" />
+
+
+
+
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 @@
+
+
+
+
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(
- 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(QtAndroid::applicationClass(), "openURL",
+ QAndroidApplication::context(), url.object(), mimeString.object());
+ };
+
+ 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("getPackageName").toString();
+ const auto providerName = QJniObject::fromString(appId + ".qtprovider"_L1);
+
+ const auto urlPath = QJniObject::fromString(url.path());
+ const auto urlFile = QJniObject(QtJniTypes::className(),
+ urlPath.object());
+
+ const auto fileProviderUri = QJniObject::callStaticMethod(
+ QtJniTypes::className(), "getUriForFile",
+ QAndroidApplication::context(), providerName.object(),
+ urlFile.object());
+
+ if (fileProviderUri.isValid())
+ return openUrl(fileProviderUri.callMethod("toString"));
+
+ return false;
}
bool QAndroidPlatformServices::openDocument(const QUrl &url)