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>
bb10
Assam Boudjelthia 2022-08-24 21:06:45 +03:00
parent b8d51001f7
commit 738c48244b
8 changed files with 70 additions and 6 deletions

View File

@ -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

View File

@ -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>

View File

@ -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")

View File

@ -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 {

View File

@ -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>

View File

@ -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
*/

View File

@ -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)

View File

@ -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)