summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJuha Vuolle <[email protected]>2024-09-02 10:21:04 +0300
committerJuha Vuolle <[email protected]>2024-09-16 11:59:49 +0300
commitc76c888556d21f93fc087b52e96a1b7f06465a2a (patch)
treeaee53c1c1daa73cf87a74939b93d6e0cac6dddf2
parent3af20bd8eb8c75017c5d6d138d7c42914ee5bee3 (diff)
Introduce qt_add_android_permission CMake function
qt_add_android_permission function can be used to set Android permissions on target executable. This allows setting new permissions, or overriding permissions set by Qt modules, without needing to supply a manual application AndroidManifest.xml. The change consists of: - New public CMake function for setting the permissions on the target + documentation - Writing these application permissions into the deployment settings json file - Reading and handling these permissions at androiddeployqt side - Moving some pre-existing permission functionality from QtAndroidHelpers.cmake to Qt6AndroidMacros.cmake so that they can be reused also in the context of application CMakeLists.txt processing - Documentation update for Android permission handling In future this same mechanism can be extended for Android features. [ChangeLog][CMake] Added qt_add_android_permission function for setting Android permissions from application CMake Fixes: QTBUG-128280 Change-Id: Ia22951fb435598be00b5da5eae11b9f35f704795 Reviewed-by: Assam Boudjelthia <[email protected]> Reviewed-by: Alexey Edelev <[email protected]>
-rw-r--r--cmake/QtAndroidHelpers.cmake39
-rw-r--r--src/android/templates/doc/src/android-manifest-file-configuration.qdoc12
-rw-r--r--src/corelib/Qt6AndroidMacros.cmake88
-rw-r--r--src/corelib/doc/snippets/cmake-macros/examples.cmake15
-rw-r--r--src/corelib/doc/src/cmake/qt_add_android_permission.qdoc38
-rw-r--r--src/tools/androiddeployqt/main.cpp39
-rw-r--r--tests/auto/other/android_deployment_settings/CMakeLists.txt9
-rw-r--r--tests/auto/other/android_deployment_settings/tst_android_deployment_settings.cpp19
8 files changed, 211 insertions, 48 deletions
diff --git a/cmake/QtAndroidHelpers.cmake b/cmake/QtAndroidHelpers.cmake
index 9e81e862058..81b5817967c 100644
--- a/cmake/QtAndroidHelpers.cmake
+++ b/cmake/QtAndroidHelpers.cmake
@@ -89,40 +89,7 @@ macro(qt_internal_setup_android_target_properties)
endmacro()
function(qt_internal_add_android_permission target)
- cmake_parse_arguments(arg "" "NAME" "ATTRIBUTES" ${ARGN})
-
- if(NOT target)
- message(FATAL_ERROR "Target for adding Android permission cannot be empty (${arg_NAME})")
- endif()
-
- if(NOT arg_NAME)
- message(FATAL_ERROR "NAME for adding Android permission cannot be empty (${target})")
- endif()
-
- set(permission_entry "${arg_NAME}")
-
- if(arg_ATTRIBUTES)
- # Permission with additional attributes
- list(LENGTH arg_ATTRIBUTES attributes_len)
- math(EXPR attributes_modulus "${attributes_len} % 2")
- if(NOT (attributes_len GREATER 1 AND attributes_modulus EQUAL 0))
- message(FATAL_ERROR "Android permission attributes must be name-value pairs (${arg_NAME})")
- endif()
- # Combine name-value pairs
- set(index 0)
- set(attributes "")
- while(index LESS attributes_len)
- list(GET arg_ATTRIBUTES ${index} name)
- math(EXPR index "${index} + 1")
- list(GET arg_ATTRIBUTES ${index} value)
- string(APPEND attributes " android:${name}=\"${value}\"")
- math(EXPR index "${index} + 1")
- endwhile()
- set(permission_entry "${permission_entry}\;${attributes}")
- endif()
-
- # Append the permission to the target's property
- set_property(TARGET ${target} APPEND PROPERTY QT_ANDROID_PERMISSIONS "${permission_entry}")
+ _qt_internal_add_android_permission(${ARGV})
endfunction()
@@ -233,9 +200,9 @@ function(qt_internal_android_dependencies_content target file_content_out)
elseif(permission_len EQUAL 2)
list(GET permission 0 name)
list(GET permission 1 extras)
- string(APPEND file_contents "<permission name=\"${name}\" extras=\'${extras}\'/>\n")
+ string(APPEND file_contents "<permission name=\"${name}\" extras=\"${extras}\"/>\n")
else()
- message(FATAL_ERROR "Unexpected permission: " "${permission}" "${permission_len}")
+ message(FATAL_ERROR "Invalid permission format: ${permission} ${permission_len}")
endif()
endforeach()
endif()
diff --git a/src/android/templates/doc/src/android-manifest-file-configuration.qdoc b/src/android/templates/doc/src/android-manifest-file-configuration.qdoc
index 4cf03564ee2..51a4679fb72 100644
--- a/src/android/templates/doc/src/android-manifest-file-configuration.qdoc
+++ b/src/android/templates/doc/src/android-manifest-file-configuration.qdoc
@@ -258,10 +258,14 @@ Since Qt 6.9, it is possible to override the default permissions set
by Qt modules. This is useful if you need to define the same permissions
as used by a Qt module, but with additional or different attributes.
-To achieve this, you can manually define these permissions in the Android
-manifest file, along with the \c {<!-- %%INSERT_PERMISSIONS -->} placeholder.
-Manually defined permissions take precedence over the same permissions added
-by Qt modules, avoiding duplication.
+There are two ways to achieve this. First way is to use
+\l {qt_add_android_permission} CMake function in the application's
+\c {CMakeLists.txt}. Permissions defined this way take precedence over
+the same permissions defined by Qt modules, avoiding duplication.
+
+Second way is to manually define these permissions in the Android
+manifest file. Permissions defined this way take precedence over permissions
+set by Qt modules, or set with \l {qt_add_android_permission}.
\section2 Style Extraction
diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake
index a03fbd270dd..6d40f3ba5da 100644
--- a/src/corelib/Qt6AndroidMacros.cmake
+++ b/src/corelib/Qt6AndroidMacros.cmake
@@ -50,6 +50,43 @@ function(_qt_internal_add_tool_to_android_deployment_settings out_var tool json_
set(${out_var} "${${out_var}}" PARENT_SCOPE)
endfunction()
+# Generates a JSON array of permissions that the 'target' may have,
+# returns an empty JSON array if no permissions were found.
+function(_qt_internal_generate_android_permissions_json out_result target)
+
+ set(${out_result} "[]" PARENT_SCOPE)
+
+ if(NOT TARGET ${target})
+ return()
+ endif()
+
+ get_target_property(permissions ${target} QT_ANDROID_PERMISSIONS)
+ if(NOT permissions)
+ return()
+ endif()
+
+ set(result "[")
+ set(json_objects "")
+ foreach(permission IN LISTS permissions)
+ # Check if the permission has also extra attributes in addition to the permission name
+ list(LENGTH permission permission_len)
+ if(permission_len EQUAL 1)
+ list(APPEND json_objects "{ \"name\": \"${permission}\" }")
+ elseif(permission_len EQUAL 2)
+ list(GET permission 0 name)
+ list(GET permission 1 extras)
+ list(APPEND json_objects "{ \"name\": \"${name}\", \"extras\": \"${extras}\" }")
+ else()
+ message(FATAL_ERROR "Invalid permission format: ${permission} ${permission_len}")
+ endif()
+ endforeach()
+
+ # Join all JSON objects with a comma. This also avoids trailing commas JSON doesn't accept
+ string(JOIN ",\n " joined_json_objects ${json_objects})
+ string(APPEND result "\n ${joined_json_objects}\n ]")
+ set(${out_result} "${result}" PARENT_SCOPE)
+endfunction()
+
# Generate the deployment settings json file for a cmake target.
function(qt6_android_generate_deployment_settings target)
# Information extracted from mkspecs/features/android/android_deployment_settings.prf
@@ -257,6 +294,9 @@ function(qt6_android_generate_deployment_settings target)
__qt_internal_collect_plugin_library_files("${target}" "${plugin_targets}" plugin_targets)
string(APPEND file_contents " \"android-deploy-plugins\":\"${plugin_targets}\",\n")
+ _qt_internal_generate_android_permissions_json(permissions_json_array "${target}")
+ string(APPEND file_contents " \"permissions\": ${permissions_json_array},\n")
+
# App binary
string(APPEND file_contents
" \"application-binary\": \"${target_output_name}\",\n")
@@ -345,6 +385,54 @@ if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
endfunction()
endif()
+function(_qt_internal_add_android_permission target)
+ if(NOT TARGET ${target})
+ message(FATAL_ERROR "Empty or invalid target for adding Android permission: (${target})")
+ endif()
+
+ cmake_parse_arguments(arg "" "NAME" "ATTRIBUTES" ${ARGN})
+
+ if(NOT arg_NAME)
+ message(FATAL_ERROR "NAME for adding Android permission cannot be empty (${target})")
+ endif()
+
+ set(permission_entry "${arg_NAME}")
+
+ if(arg_ATTRIBUTES)
+ # Permission with additional attributes
+ list(LENGTH arg_ATTRIBUTES attributes_len)
+ math(EXPR attributes_modulus "${attributes_len} % 2")
+ if(NOT (attributes_len GREATER 1 AND attributes_modulus EQUAL 0))
+ message(FATAL_ERROR "Android permission: ${arg_NAME} attributes: ${arg_ATTRIBUTES} must"
+ " be name-value pairs (for example: minSdkVersion 30)")
+ endif()
+ # Combine name-value pairs
+ set(index 0)
+ set(attributes "")
+ while(index LESS attributes_len)
+ list(GET arg_ATTRIBUTES ${index} name)
+ math(EXPR index "${index} + 1")
+ list(GET arg_ATTRIBUTES ${index} value)
+ string(APPEND attributes "android:${name}=\'${value}\' ")
+ math(EXPR index "${index} + 1")
+ endwhile()
+ set(permission_entry "${permission_entry}\;${attributes}")
+ endif()
+
+ # Append the permission to the target's property
+ set_property(TARGET ${target} APPEND PROPERTY QT_ANDROID_PERMISSIONS "${permission_entry}")
+endfunction()
+
+function(qt6_add_android_permission target)
+ _qt_internal_add_android_permission(${ARGV})
+endfunction()
+
+if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
+ function(qt_add_android_permission target)
+ qt6_add_android_permission(${ARGV})
+ endfunction()
+endif()
+
function(qt6_android_apply_arch_suffix target)
get_target_property(called_from_qt_impl
${target} _qt_android_apply_arch_suffix_called_from_qt_impl)
diff --git a/src/corelib/doc/snippets/cmake-macros/examples.cmake b/src/corelib/doc/snippets/cmake-macros/examples.cmake
index 835d45bc089..db6bc5c6cd9 100644
--- a/src/corelib/doc/snippets/cmake-macros/examples.cmake
+++ b/src/corelib/doc/snippets/cmake-macros/examples.cmake
@@ -92,6 +92,21 @@ qt_android_generate_deployment_settings(myapp)
qt_android_add_apk_target(myapp)
#! [qt_android_deploy_basic]
+#! [qt_add_android_permission]
+qt_add_executable(myapp
+ // ...
+)
+qt_add_android_permission(myapp
+ NAME android.permission.BLUETOOTH_SCAN
+ ATTRIBUTES
+ minSdkVersion 31
+ usesPermissionFlags neverForLocation
+)
+qt_add_android_permission(myapp
+ NAME android.permission.ACCESS_COARSE_LOCATION
+)
+#! [qt_add_android_permission]
+
#! [qt_finalize_project_manual]
cmake_minimum_required(VERSIONS 3.16)
diff --git a/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc b/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc
new file mode 100644
index 00000000000..68fe6a8e5cc
--- /dev/null
+++ b/src/corelib/doc/src/cmake/qt_add_android_permission.qdoc
@@ -0,0 +1,38 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+\page qt-add-android-permission.html
+\ingroup cmake-commands-qtcore
+
+\title qt_add_android_permission
+\keyword qt6_add_android_permission
+
+\summary {Adds an Android permission to the target executable.}
+
+\include cmake-find-package-core.qdocinc
+
+\cmakecommandsince 6.9
+
+\section1 Synopsis
+
+\badcode
+qt_add_android_permission(target NAME <permission-name> [ATTRIBUTES <name1> <value1> ...])
+\endcode
+
+\versionlessCMakeCommandsNote qt6_add_android_permission()
+
+\section1 Description
+
+The command adds an Android permission to the \c {target} executable.
+This can be used to define additional permissions, or overriding
+the default permissions set by Qt modules.
+
+For further information on defining Android permissions,
+see \l {Qt Permissions and Features}.
+
+\section1 Example
+
+\snippet cmake-macros/examples.cmake qt_add_android_permission
+
+*/
diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp
index 5f8a8454b41..98324ab5b25 100644
--- a/src/tools/androiddeployqt/main.cpp
+++ b/src/tools/androiddeployqt/main.cpp
@@ -242,7 +242,8 @@ struct Options
// Per package collected information
// permissions 'name' => 'optional additional attributes'
- QMap<QString, QString> permissions;
+ QMap<QString, QString> modulePermissions;
+ QMap<QString, QString> applicationPermissions;
QStringList features;
// Override qml import scanner path
@@ -1457,6 +1458,21 @@ bool readInputFile(Options *options)
}
}
+ {
+ QJsonArray permissions = jsonObject.value("permissions"_L1).toArray();
+ if (!permissions.isEmpty()) {
+ for (const QJsonValue &value : permissions) {
+ if (value.isObject()) {
+ QJsonObject permissionObj = value.toObject();
+ QString name = permissionObj.value("name"_L1).toString();
+ QString extras;
+ if (permissionObj.contains("extras"_L1))
+ extras = permissionObj.value("extras"_L1).toString().trimmed();
+ options->applicationPermissions.insert(name, extras);
+ }
+ }
+ }
+ }
return true;
}
@@ -1896,14 +1912,23 @@ bool updateAndroidManifest(Options &options)
QXmlStreamReader reader(&androidManifestXml);
while (!reader.atEnd()) {
reader.readNext();
- if (reader.isStartElement() && reader.name() == "uses-permission"_L1)
- options.permissions.remove(QString(reader.attributes().value("android:name"_L1)));
+ if (reader.isStartElement() && reader.name() == "uses-permission"_L1) {
+ options.modulePermissions.remove(
+ QString(reader.attributes().value("android:name"_L1)));
+ options.applicationPermissions.remove(
+ QString(reader.attributes().value("android:name"_L1)));
+ }
}
androidManifestXml.close();
}
+ // Application may define permissions in its CMakeLists.txt, give them the priority
+ QMap<QString, QString> resolvedPermissions = options.modulePermissions;
+ for (auto [name, extras] : options.applicationPermissions.asKeyValueRange())
+ resolvedPermissions.insert(name, extras);
+
QString permissions;
- for (auto [name, extras] : options.permissions.asKeyValueRange())
+ for (auto [name, extras] : resolvedPermissions.asKeyValueRange())
permissions += " <uses-permission android:name=\"%1\" %2 />\n"_L1.arg(name).arg(extras);
replacements[QStringLiteral("<!-- %%INSERT_PERMISSIONS -->")] = permissions.trimmed();
@@ -2172,9 +2197,9 @@ bool readAndroidDependencyXml(Options *options,
QString extras = reader.attributes().value("extras"_L1).toString();
// With duplicate permissions prioritize the one without any attributes,
// as that is likely the most permissive
- if (!options->permissions.contains(name)
- || !options->permissions.value(name).isEmpty()) {
- options->permissions.insert(name, extras);
+ if (!options->modulePermissions.contains(name)
+ || !options->modulePermissions.value(name).isEmpty()) {
+ options->modulePermissions.insert(name, extras);
}
} else if (reader.name() == "feature"_L1) {
QString name = reader.attributes().value("name"_L1).toString();
diff --git a/tests/auto/other/android_deployment_settings/CMakeLists.txt b/tests/auto/other/android_deployment_settings/CMakeLists.txt
index 613b1f29096..cd11c2c5e9d 100644
--- a/tests/auto/other/android_deployment_settings/CMakeLists.txt
+++ b/tests/auto/other/android_deployment_settings/CMakeLists.txt
@@ -18,6 +18,13 @@ function(tst_generate_android_deployment_setting target)
qt6_android_generate_deployment_settings(${target})
endfunction()
+function(tst_add_android_permissions target)
+ qt6_add_android_permission(${target} NAME PERMISSION_WITH_ATTRIBUTES
+ ATTRIBUTES
+ minSdkVersion 32 maxSdkVersion 34)
+ qt6_add_android_permission(${target} NAME PERMISSION_WITHOUT_ATTRIBUTES)
+endfunction()
+
qt6_policy(SET QTP0002 NEW)
set(target tst_android_deployment_settings_new)
@@ -46,6 +53,7 @@ set_target_properties(${target} PROPERTIES
# qt6_android_generate_deployment_settings
QT_ANDROID_DEPLOYMENT_SETTINGS_FILE "custom_deployment_settings.json"
)
+tst_add_android_permissions(${target})
tst_generate_android_deployment_setting(${target})
qt6_policy(SET QTP0002 OLD)
@@ -67,6 +75,7 @@ set_target_properties(${target} PROPERTIES
QT_ANDROID_PACKAGE_SOURCE_DIR "path\\to/source\\dir"
QT_ANDROID_SYSTEM_LIBS_PREFIX "myLibPrefix"
)
+tst_add_android_permissions(${target})
tst_generate_android_deployment_setting(${target})
get_target_property(new_settings
diff --git a/tests/auto/other/android_deployment_settings/tst_android_deployment_settings.cpp b/tests/auto/other/android_deployment_settings/tst_android_deployment_settings.cpp
index 43797245259..1687ed9de92 100644
--- a/tests/auto/other/android_deployment_settings/tst_android_deployment_settings.cpp
+++ b/tests/auto/other/android_deployment_settings/tst_android_deployment_settings.cpp
@@ -1,6 +1,7 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QFile>
@@ -82,13 +83,29 @@ void tst_android_deployment_settings::DeploymentSettings_data()
<< "org.qtproject.android_deployment_settings_test";
QTest::newRow("android-app-name") << "android-app-name"
<< "Android Deployment Settings Test";
+ QTest::newRow("permissions") << "permissions"
+ << "[{\"name\":\"PERMISSION_WITH_ATTRIBUTES\","
+ "\"extras\":\"android:minSdkVersion='32' android:maxSdkVersion='34' \"},"
+ "{\"name\":\"PERMISSION_WITHOUT_ATTRIBUTES\"}]";
}
void tst_android_deployment_settings::DeploymentSettings()
{
QFETCH(QString, key);
QFETCH(QString, value);
- QCOMPARE(jsonDoc[key].toString(), value);
+ QJsonValue keyValue = jsonDoc[key];
+ if (keyValue.type() == QJsonValue::Type::String) {
+ QCOMPARE(keyValue.toString(), value);
+ } else if (keyValue.type() == QJsonValue::Type::Array) {
+ QJsonParseError parseError;
+ // For robustness (field order, whitespaces etc.) make comparison between QJsonDocuments
+ QJsonDocument expectedDoc = QJsonDocument::fromJson(value.toUtf8(), &parseError);
+ if (parseError.error != QJsonParseError::NoError)
+ qFatal("Failed to parse expected JSON array: %s", qPrintable(parseError.errorString()));
+ QCOMPARE(QJsonDocument(keyValue.toArray()), expectedDoc);
+ } else {
+ qFatal("Unhandled JSON type: %i", keyValue.type());
+ }
}
void tst_android_deployment_settings::QtPaths_data()