From a5897d579bb0af496a18f7430345a67fe27ff0df Mon Sep 17 00:00:00 2001 From: Fredia Huya-Kouadio Date: Sun, 16 Jun 2024 12:14:34 -0700 Subject: [PATCH] Update the `GodotHost` interface to support signing and verifying Android apks Update the export logic to enable apk generation and signing for Android editor builds Note: Only legacy builds are supported. Gradle builds are not supported at this point in time. --- .pre-commit-config.yaml | 1 + COPYRIGHT.txt | 3 +- editor/editor_node.cpp | 2 - editor/editor_paths.cpp | 4 + editor/export/editor_export_platform.cpp | 2 - editor/export/export_template_manager.cpp | 6 +- editor/export/export_template_manager.h | 1 - editor/export/project_export.cpp | 14 +-- platform/android/dir_access_jandroid.cpp | 10 +- platform/android/dir_access_jandroid.h | 2 +- platform/android/export/export.cpp | 11 ++- platform/android/export/export_plugin.cpp | 92 ++++++++++++------ platform/android/file_access_android.h | 2 +- .../android/file_access_filesystem_jandroid.h | 2 +- platform/android/java/{lib => }/THIRDPARTY.md | 28 +++--- .../src/main/assets/keystores/debug.keystore | Bin 0 -> 2714 bytes .../lib/src/org/godotengine/godot/Godot.kt | 18 +++- .../org/godotengine/godot/GodotFragment.java | 17 ++++ .../src/org/godotengine/godot/GodotHost.java | 28 ++++++ .../org/godotengine/godot/io/StorageScope.kt | 8 ++ .../godot/io/file/FileAccessHandler.kt | 2 +- platform/android/java_godot_wrapper.cpp | 41 ++++++++ platform/android/java_godot_wrapper.h | 6 ++ platform/android/os_android.cpp | 10 ++ platform/android/os_android.h | 5 + platform/web/export/export.cpp | 2 - 26 files changed, 241 insertions(+), 76 deletions(-) rename platform/android/java/{lib => }/THIRDPARTY.md (57%) create mode 100644 platform/android/java/editor/src/main/assets/keystores/debug.keystore diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 46f29d0d5fc..56ebd9e2c76 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -162,6 +162,7 @@ repos: modules/gdscript/tests/scripts/parser/features/mixed_indentation_on_blank_lines\.gd$| modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment\.notest\.gd$| modules/gdscript/tests/scripts/parser/warnings/empty_file_newline\.notest\.gd$| + platform/android/java/editor/src/main/java/com/android/.*| platform/android/java/lib/src/com/google/.* ) diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index a9d6cd7d320..5b6dcbb567b 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -70,7 +70,8 @@ Copyright: 2020, Manuel Prandini 2007-2014, Juan Linietsky, Ariel Manzur License: Expat -Files: ./platform/android/java/lib/aidl/com/android/* +Files: ./platform/android/java/editor/src/main/java/com/android/* + ./platform/android/java/lib/aidl/com/android/* ./platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml ./platform/android/java/lib/src/com/google/android/* ./platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index a8f8edaae88..6a7580b4b66 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -7319,9 +7319,7 @@ EditorNode::EditorNode() { #endif settings_menu->add_item(TTR("Manage Editor Features..."), SETTINGS_MANAGE_FEATURE_PROFILES); -#ifndef ANDROID_ENABLED settings_menu->add_item(TTR("Manage Export Templates..."), SETTINGS_MANAGE_EXPORT_TEMPLATES); -#endif #if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED) settings_menu->add_item(TTR("Configure FBX Importer..."), SETTINGS_MANAGE_FBX_IMPORTER); #endif diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp index be511452a68..7f24e8fd2e4 100644 --- a/editor/editor_paths.cpp +++ b/editor/editor_paths.cpp @@ -71,7 +71,11 @@ String EditorPaths::get_export_templates_dir() const { } String EditorPaths::get_debug_keystore_path() const { +#ifdef ANDROID_ENABLED + return "assets://keystores/debug.keystore"; +#else return get_data_dir().path_join("keystores/debug.keystore"); +#endif } String EditorPaths::get_project_settings_dir() const { diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index 0768ae128bc..8b31eda3bc7 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -1861,7 +1861,6 @@ void EditorExportPlatform::gen_export_flags(Vector &r_flags, int p_flags bool EditorExportPlatform::can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { bool valid = true; -#ifndef ANDROID_ENABLED String templates_error; valid = valid && has_valid_export_configuration(p_preset, templates_error, r_missing_templates, p_debug); @@ -1886,7 +1885,6 @@ bool EditorExportPlatform::can_export(const Ref &p_preset, S if (!export_plugins_warning.is_empty()) { r_error += export_plugins_warning; } -#endif String project_configuration_error; valid = valid && has_valid_project_configuration(p_preset, project_configuration_error); diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp index 0caf0ee0665..9dfbe515d23 100644 --- a/editor/export/export_template_manager.cpp +++ b/editor/export/export_template_manager.cpp @@ -111,7 +111,9 @@ void ExportTemplateManager::_update_template_status() { TreeItem *ti = installed_table->create_item(installed_root); ti->set_text(0, version_string); +#ifndef ANDROID_ENABLED ti->add_button(0, get_editor_theme_icon(SNAME("Folder")), OPEN_TEMPLATE_FOLDER, false, TTR("Open the folder containing these templates.")); +#endif ti->add_button(0, get_editor_theme_icon(SNAME("Remove")), UNINSTALL_TEMPLATE, false, TTR("Uninstall these templates.")); } } @@ -921,11 +923,13 @@ ExportTemplateManager::ExportTemplateManager() { current_installed_path->set_h_size_flags(Control::SIZE_EXPAND_FILL); current_installed_hb->add_child(current_installed_path); - current_open_button = memnew(Button); +#ifndef ANDROID_ENABLED + Button *current_open_button = memnew(Button); current_open_button->set_text(TTR("Open Folder")); current_open_button->set_tooltip_text(TTR("Open the folder containing installed templates for the current version.")); current_installed_hb->add_child(current_open_button); current_open_button->connect(SceneStringName(pressed), callable_mp(this, &ExportTemplateManager::_open_template_folder).bind(VERSION_FULL_CONFIG)); +#endif current_uninstall_button = memnew(Button); current_uninstall_button->set_text(TTR("Uninstall")); diff --git a/editor/export/export_template_manager.h b/editor/export/export_template_manager.h index b1c5855878b..5227e43a6ea 100644 --- a/editor/export/export_template_manager.h +++ b/editor/export/export_template_manager.h @@ -58,7 +58,6 @@ class ExportTemplateManager : public AcceptDialog { HBoxContainer *current_installed_hb = nullptr; LineEdit *current_installed_path = nullptr; - Button *current_open_button = nullptr; Button *current_uninstall_button = nullptr; VBoxContainer *install_options_vb = nullptr; diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index 3103e504b9c..351afa38109 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -1147,10 +1147,8 @@ void ProjectExportDialog::_export_project_to_path(const String &p_path) { } void ProjectExportDialog::_export_all_dialog() { -#ifndef ANDROID_ENABLED export_all_dialog->show(); export_all_dialog->popup_centered(Size2(300, 80)); -#endif } void ProjectExportDialog::_export_all_dialog_action(const String &p_str) { @@ -1491,13 +1489,9 @@ ProjectExportDialog::ProjectExportDialog() { set_ok_button_text(TTR("Export PCK/ZIP...")); get_ok_button()->set_tooltip_text(TTR("Export the project resources as a PCK or ZIP package. This is not a playable build, only the project data without a Godot executable.")); get_ok_button()->set_disabled(true); -#ifdef ANDROID_ENABLED - export_button = memnew(Button); - export_button->hide(); -#else + export_button = add_button(TTR("Export Project..."), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "export"); export_button->set_tooltip_text(TTR("Export the project as a playable build (Godot executable and project data) for the selected preset.")); -#endif export_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectExportDialog::_export_project)); // Disable initially before we select a valid preset export_button->set_disabled(true); @@ -1510,14 +1504,8 @@ ProjectExportDialog::ProjectExportDialog() { export_all_dialog->add_button(TTR("Debug"), true, "debug"); export_all_dialog->add_button(TTR("Release"), true, "release"); export_all_dialog->connect("custom_action", callable_mp(this, &ProjectExportDialog::_export_all_dialog_action)); -#ifdef ANDROID_ENABLED - export_all_dialog->hide(); - export_all_button = memnew(Button); - export_all_button->hide(); -#else export_all_button = add_button(TTR("Export All..."), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "export"); -#endif export_all_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectExportDialog::_export_all_dialog)); export_all_button->set_disabled(true); diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index 56d41f2ab72..19c18eb96e3 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -218,7 +218,7 @@ bool DirAccessJAndroid::dir_exists(String p_dir) { } } -Error DirAccessJAndroid::make_dir_recursive(const String &p_dir) { +Error DirAccessJAndroid::make_dir(String p_dir) { // Check if the directory exists already if (dir_exists(p_dir)) { return ERR_ALREADY_EXISTS; @@ -242,8 +242,12 @@ Error DirAccessJAndroid::make_dir_recursive(const String &p_dir) { } } -Error DirAccessJAndroid::make_dir(String p_dir) { - return make_dir_recursive(p_dir); +Error DirAccessJAndroid::make_dir_recursive(const String &p_dir) { + Error err = make_dir(p_dir); + if (err != OK && err != ERR_ALREADY_EXISTS) { + ERR_FAIL_V_MSG(err, "Could not create directory: " + p_dir); + } + return OK; } Error DirAccessJAndroid::rename(String p_from, String p_to) { diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h index 68578b0fa92..1d8fe906f37 100644 --- a/platform/android/dir_access_jandroid.h +++ b/platform/android/dir_access_jandroid.h @@ -84,7 +84,7 @@ public: virtual bool is_link(String p_file) override { return false; } virtual String read_link(String p_file) override { return p_file; } - virtual Error create_link(String p_source, String p_target) override { return FAILED; } + virtual Error create_link(String p_source, String p_target) override { return ERR_UNAVAILABLE; } virtual uint64_t get_space_left() override; diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 6a6d7149ff8..3f4624d09c3 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -42,16 +42,17 @@ void register_android_exporter_types() { } void register_android_exporter() { -#ifndef ANDROID_ENABLED - EDITOR_DEF("export/android/java_sdk_path", OS::get_singleton()->get_environment("JAVA_HOME")); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/java_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); - EDITOR_DEF("export/android/android_sdk_path", OS::get_singleton()->get_environment("ANDROID_HOME")); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); EDITOR_DEF("export/android/debug_keystore", EditorPaths::get_singleton()->get_debug_keystore_path()); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks")); EDITOR_DEF("export/android/debug_keystore_user", DEFAULT_ANDROID_KEYSTORE_DEBUG_USER); EDITOR_DEF("export/android/debug_keystore_pass", DEFAULT_ANDROID_KEYSTORE_DEBUG_PASSWORD); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore_pass", PROPERTY_HINT_PASSWORD)); + +#ifndef ANDROID_ENABLED + EDITOR_DEF("export/android/java_sdk_path", OS::get_singleton()->get_environment("JAVA_HOME")); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/java_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); + EDITOR_DEF("export/android/android_sdk_path", OS::get_singleton()->get_environment("ANDROID_HOME")); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); EDITOR_DEF("export/android/force_system_user", false); EDITOR_DEF("export/android/shutdown_adb_on_exit", true); diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 689360aef6c..0fdaca48392 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -57,6 +57,10 @@ #include "modules/svg/image_loader_svg.h" #endif +#ifdef ANDROID_ENABLED +#include "../os_android.h" +#endif + #include static const char *android_perms[] = { @@ -2417,6 +2421,10 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Refget("gradle_build/android_source_template"); @@ -2439,6 +2447,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref &p_preset, bool p_debug, const String &export_path, EditorProgress &ep) { int export_format = int(p_preset->get("gradle_build/export_format")); - String export_label = export_format == EXPORT_FORMAT_AAB ? "AAB" : "APK"; - String release_keystore = _get_keystore_path(p_preset, false); - String release_username = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER); - String release_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS); - String target_sdk_version = p_preset->get("gradle_build/target_sdk"); - if (!target_sdk_version.is_valid_int()) { - target_sdk_version = itos(DEFAULT_TARGET_SDK_VERSION); - } - String apksigner = get_apksigner_path(target_sdk_version.to_int(), true); - print_verbose("Starting signing of the " + export_label + " binary using " + apksigner); - if (apksigner == "") { - add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("All 'apksigner' tools located in Android SDK 'build-tools' directory failed to execute. Please check that you have the correct version installed for your target sdk version. The resulting %s is unsigned."), export_label)); - return OK; - } - if (!FileAccess::exists(apksigner)) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("'apksigner' could not be found. Please check that the command is available in the Android SDK build-tools directory. The resulting %s is unsigned."), export_label)); - return OK; + if (export_format == EXPORT_FORMAT_AAB) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("AAB signing is not supported")); + return FAILED; } String keystore; @@ -2750,15 +2747,15 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref &p_pre user = EDITOR_GET("export/android/debug_keystore_user"); } - if (ep.step(vformat(TTR("Signing debug %s..."), export_label), 104)) { + if (ep.step(TTR("Signing debug APK..."), 104)) { return ERR_SKIP; } } else { - keystore = release_keystore; - password = release_password; - user = release_username; + keystore = _get_keystore_path(p_preset, false); + password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS); + user = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER); - if (ep.step(vformat(TTR("Signing release %s..."), export_label), 104)) { + if (ep.step(TTR("Signing release APK..."), 104)) { return ERR_SKIP; } } @@ -2768,6 +2765,36 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref &p_pre return ERR_FILE_CANT_OPEN; } + String apk_path = export_path; + if (apk_path.is_relative_path()) { + apk_path = OS::get_singleton()->get_resource_dir().path_join(apk_path); + } + apk_path = ProjectSettings::get_singleton()->globalize_path(apk_path).simplify_path(); + + Error err; +#ifdef ANDROID_ENABLED + err = OS_Android::get_singleton()->sign_apk(apk_path, apk_path, keystore, user, password); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Unable to sign apk.")); + return err; + } +#else + String target_sdk_version = p_preset->get("gradle_build/target_sdk"); + if (!target_sdk_version.is_valid_int()) { + target_sdk_version = itos(DEFAULT_TARGET_SDK_VERSION); + } + + String apksigner = get_apksigner_path(target_sdk_version.to_int(), true); + print_verbose("Starting signing of the APK binary using " + apksigner); + if (apksigner == "") { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("All 'apksigner' tools located in Android SDK 'build-tools' directory failed to execute. Please check that you have the correct version installed for your target sdk version. The resulting APK is unsigned.")); + return OK; + } + if (!FileAccess::exists(apksigner)) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("'apksigner' could not be found. Please check that the command is available in the Android SDK build-tools directory. The resulting APK is unsigned.")); + return OK; + } + String output; List args; args.push_back("sign"); @@ -2778,7 +2805,7 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref &p_pre args.push_back("pass:" + password); args.push_back("--ks-key-alias"); args.push_back(user); - args.push_back(export_path); + args.push_back(apk_path); if (OS::get_singleton()->is_stdout_verbose() && p_debug) { // We only print verbose logs with credentials for debug builds to avoid leaking release keystore credentials. print_verbose("Signing debug binary using: " + String("\n") + apksigner + " " + join_list(args, String(" "))); @@ -2790,7 +2817,7 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref &p_pre print_line("Signing binary using: " + String("\n") + apksigner + " " + join_list(redacted_args, String(" "))); } int retval; - Error err = OS::get_singleton()->execute(apksigner, args, &output, &retval, true); + err = OS::get_singleton()->execute(apksigner, args, &output, &retval, true); if (err != OK) { add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start apksigner executable.")); return err; @@ -2802,15 +2829,23 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref &p_pre add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("output: \n%s"), output)); return ERR_CANT_CREATE; } +#endif - if (ep.step(vformat(TTR("Verifying %s..."), export_label), 105)) { + if (ep.step(TTR("Verifying APK..."), 105)) { return ERR_SKIP; } +#ifdef ANDROID_ENABLED + err = OS_Android::get_singleton()->verify_apk(apk_path); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Unable to verify signed apk.")); + return err; + } +#else args.clear(); args.push_back("verify"); args.push_back("--verbose"); - args.push_back(export_path); + args.push_back(apk_path); if (p_debug) { print_verbose("Verifying signed build using: " + String("\n") + apksigner + " " + join_list(args, String(" "))); } @@ -2823,10 +2858,11 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref &p_pre } print_verbose(output); if (retval) { - add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("'apksigner' verification of %s failed."), export_label)); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("'apksigner' verification of APK failed.")); add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("output: \n%s"), output)); return ERR_CANT_CREATE; } +#endif print_verbose("Successfully completed signing build."); return OK; @@ -3319,7 +3355,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref _get_unix_permissions(const String &p_file) override { return 0; } - virtual Error _set_unix_permissions(const String &p_file, BitField p_permissions) override { return FAILED; } + virtual Error _set_unix_permissions(const String &p_file, BitField p_permissions) override { return ERR_UNAVAILABLE; } virtual bool _get_hidden_attribute(const String &p_file) override { return false; } virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; } diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h index 6a8fc524b77..2795ac02ac0 100644 --- a/platform/android/file_access_filesystem_jandroid.h +++ b/platform/android/file_access_filesystem_jandroid.h @@ -101,7 +101,7 @@ public: virtual uint64_t _get_modified_time(const String &p_file) override; virtual BitField _get_unix_permissions(const String &p_file) override { return 0; } - virtual Error _set_unix_permissions(const String &p_file, BitField p_permissions) override { return FAILED; } + virtual Error _set_unix_permissions(const String &p_file, BitField p_permissions) override { return ERR_UNAVAILABLE; } virtual bool _get_hidden_attribute(const String &p_file) override { return false; } virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; } diff --git a/platform/android/java/lib/THIRDPARTY.md b/platform/android/java/THIRDPARTY.md similarity index 57% rename from platform/android/java/lib/THIRDPARTY.md rename to platform/android/java/THIRDPARTY.md index 2496b592630..7807cc55ff7 100644 --- a/platform/android/java/lib/THIRDPARTY.md +++ b/platform/android/java/THIRDPARTY.md @@ -3,14 +3,6 @@ This file list third-party libraries used in the Android source folder, with their provenance and, when relevant, modifications made to those files. -## com.android.vending.billing - -- Upstream: https://github.com/googlesamples/android-play-billing/tree/master/TrivialDrive/app/src/main -- Version: git (7a94c69, 2019) -- License: Apache 2.0 - -Overwrite the file `aidl/com/android/vending/billing/IInAppBillingService.aidl`. - ## com.google.android.vending.expansion.downloader - Upstream: https://github.com/google/play-apk-expansion/tree/master/apkx_library @@ -19,10 +11,10 @@ Overwrite the file `aidl/com/android/vending/billing/IInAppBillingService.aidl`. Overwrite all files under: -- `src/com/google/android/vending/expansion/downloader` +- `lib/src/com/google/android/vending/expansion/downloader` Some files have been modified for yet unclear reasons. -See the `patches/com.google.android.vending.expansion.downloader.patch` file. +See the `lib/patches/com.google.android.vending.expansion.downloader.patch` file. ## com.google.android.vending.licensing @@ -32,8 +24,18 @@ See the `patches/com.google.android.vending.expansion.downloader.patch` file. Overwrite all files under: -- `aidl/com/android/vending/licensing` -- `src/com/google/android/vending/licensing` +- `lib/aidl/com/android/vending/licensing` +- `lib/src/com/google/android/vending/licensing` Some files have been modified to silence linter errors or fix downstream issues. -See the `patches/com.google.android.vending.licensing.patch` file. +See the `lib/patches/com.google.android.vending.licensing.patch` file. + +## com.android.apksig + +- Upstream: https://android.googlesource.com/platform/tools/apksig/+/ac5cbb07d87cc342fcf07715857a812305d69888 +- Version: git (ac5cbb07d87cc342fcf07715857a812305d69888, 2024) +- License: Apache 2.0 + +Overwrite all files under: + +- `editor/src/main/java/com/android/apksig` diff --git a/platform/android/java/editor/src/main/assets/keystores/debug.keystore b/platform/android/java/editor/src/main/assets/keystores/debug.keystore new file mode 100644 index 0000000000000000000000000000000000000000..3b7a97c8ee1fd9aa36c61ffb9e42a8b4c1e6b829 GIT binary patch literal 2714 zcma)8X*d*$8lD+57)&NaMg}29mKjSZvNL2273EO&!PwWimd28$j6?Q4dlJ@b^d+n$bh64{#udqE&EI0z_+W@BXj_eBsC0zmUKLhWL(Kt~2J zPy!6^9&S-6J#`BqvobB;vE?rY6Tw5~VezW4$2S_UVM*@e84gb&i+4I^ks?%z)wL|w zk*prYfd!eTg5_JN;PfQRE9X zuMhj?lJ&^?&+mLn6Y)|<7nXzB1s|On)+|j2+JCJZa17U{}I4; zikHmRpF&;?vVS0;pSv{ClvBt$LDl%HmaA{uAn(o&>BUSTiU3pDi#e9JiRejS?;*4{M`>m1ndVQbX~~RCA#Zjp1os4Ux;FTf7@L@7?7o*(V-r z3YKr~+SaQ9hWwIx5EPuffLg4*Z9<_*gD_Ol>2{uJk8ZvHvYTh=xypA+ZgPm{r)5fp z}xEa*bkkkJC)^uO0`Yufur@|9BvWBQK?vCa8Fe2n; z)wkf|6y(8v7u-YCXQ?mbwIzoNiQHIP3wB}9Gfx-ZJY5Xy;XW7)Y8-chO9x(I@e~M- zf6?t^QIIJD{zCpU+E&|d2klrm(Oz7jCm8p;wQP?bkbs)QJQSoY?#Rf=sLmEEY94ox zENWlBdKE-{m)+K;|LyC(XR^+#&WwFGv5_*wZt%XeYrA`2JiCKVS+(QP=%R1bR<#)! zBG3!hOsHY@kbf5*MrAH!rbaF3EmUOFk?e-%TBB4}Gl?mhB-TTNU+$m_m^Qzd_a@}H z=;q+S$e;y&#AiI9s^GoC0?)g^b7lusxKiH7+$>TDxjca^qs!WvC~T2;S(+_Y>&in+ zf4R9jNU6Y%tlt;`)Bu%s*q;! zf?0jslWe87MbBQn&dQqm18W^maAt=qAqby)3ydRn*{onYrh=3d_^qwfl|lB@?(fpg zYdgLk2{*?rY8o=pzHH#x7|bh9&kBj_WX+i0Xrv-f^Xp=2NjIjm z_r<=qQ34)dx!-mfq+MV3@2g(zzv%W>%=Iamrr~|lxF8m_G@jd?(ABs*uDa-O0{zzU52={{(!@B!V7abjySVepL5L{ zGLGe84^e4GTjUn$j=D)gov8-wi%Xsh!;4rFUeoFV!S~DPl=11`oxb&|!$=OYxNd-o z6yM z4uC!$l+EDBpEmEYaK+mc8um;>eGh#LAMD|~dmQPfv&NuL$A(@WE-4oa@n_WTvJz|6 zl*H0o!mGD9aNc%fC8z5w&6 zWx`Tw>c_b3Ta>os8R1NY;iBOT5sf~`)2kWGF3}V*Q3ZJGZhralzIqeCkJ4qQ5+>VH zfS-5|XG(*vD7TyzId2Los9>4gT0mj> zI%Owkv5j%^D)GI>3f|UL)m<{ctGY+gJD%<4Ax$XB7BfecoSe{*ry%JtI+Ju?MBv^81my zi>$C*?59*PxvoqQgaMwUvxa=EWwuNihSg6EHF#rRz?yd|OVaO(C!?A-S(|U63~WR+ zmt#mfng~g)CD4}mifwJ3P5r;`t@VUl;zatdhT^^XPuj)(1eC_|@$}WTOebBqu zmHb5MVy4U-ZJ3(lQu$KK`6Ai~jX}eH{%as01OOJ)%&evc1QO4CTeMUeQh={C0$5EM p+MAO5BCt^xl$hyYF6sU(1&2hB@5eV6`kX*hcU6XwlmCj8e*&=i+Q$F@ literal 0 HcmV?d00001 diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 1e0027089cf..49e8ffb008f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -50,6 +50,7 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsAnimationCompat import androidx.core.view.WindowInsetsCompat import com.google.android.vending.expansion.downloader.* +import org.godotengine.godot.error.Error import org.godotengine.godot.input.GodotEditText import org.godotengine.godot.input.GodotInputHandler import org.godotengine.godot.io.directory.DirectoryAccessHandler @@ -96,7 +97,6 @@ class Godot(private val context: Context) { fun isEditorBuild() = BuildConfig.FLAVOR == EDITOR_FLAVOR } - private val windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager private val mSensorManager: SensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager private val mClipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager private val vibratorService: Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator @@ -1054,4 +1054,20 @@ class Godot(private val context: Context) { private fun nativeDumpBenchmark(benchmarkFile: String) { dumpBenchmark(fileAccessHandler, benchmarkFile) } + + @Keep + private fun nativeSignApk(inputPath: String, + outputPath: String, + keystorePath: String, + keystoreUser: String, + keystorePassword: String): Int { + val signResult = primaryHost?.signApk(inputPath, outputPath, keystorePath, keystoreUser, keystorePassword) ?: Error.ERR_UNAVAILABLE + return signResult.toNativeValue() + } + + @Keep + private fun nativeVerifyApk(apkPath: String): Int { + val verifyResult = primaryHost?.verifyApk(apkPath) ?: Error.ERR_UNAVAILABLE + return verifyResult.toNativeValue() + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java index fdda7665947..e0f5744368c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java @@ -30,6 +30,7 @@ package org.godotengine.godot; +import org.godotengine.godot.error.Error; import org.godotengine.godot.plugin.GodotPlugin; import org.godotengine.godot.utils.BenchmarkUtils; @@ -484,4 +485,20 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH } return Collections.emptySet(); } + + @Override + public Error signApk(@NonNull String inputPath, @NonNull String outputPath, @NonNull String keystorePath, @NonNull String keystoreUser, @NonNull String keystorePassword) { + if (parentHost != null) { + return parentHost.signApk(inputPath, outputPath, keystorePath, keystoreUser, keystorePassword); + } + return Error.ERR_UNAVAILABLE; + } + + @Override + public Error verifyApk(@NonNull String apkPath) { + if (parentHost != null) { + return parentHost.verifyApk(apkPath); + } + return Error.ERR_UNAVAILABLE; + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java index 1862b9fa9be..f1c84e90a74 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java @@ -30,10 +30,13 @@ package org.godotengine.godot; +import org.godotengine.godot.error.Error; import org.godotengine.godot.plugin.GodotPlugin; import android.app.Activity; +import androidx.annotation.NonNull; + import java.util.Collections; import java.util.List; import java.util.Set; @@ -108,4 +111,29 @@ public interface GodotHost { default Set getHostPlugins(Godot engine) { return Collections.emptySet(); } + + /** + * Signs the given Android apk + * + * @param inputPath Path to the apk that should be signed + * @param outputPath Path for the signed output apk; can be the same as inputPath + * @param keystorePath Path to the keystore to use for signing the apk + * @param keystoreUser Keystore user credential + * @param keystorePassword Keystore password credential + * + * @return {@link Error#OK} if signing is successful + */ + default Error signApk(@NonNull String inputPath, @NonNull String outputPath, @NonNull String keystorePath, @NonNull String keystoreUser, @NonNull String keystorePassword) { + return Error.ERR_UNAVAILABLE; + } + + /** + * Verifies the given Android apk is signed + * + * @param apkPath Path to the apk that should be verified + * @return {@link Error#OK} if verification was successful + */ + default Error verifyApk(@NonNull String apkPath) { + return Error.ERR_UNAVAILABLE; + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt index 2e5649b563e..574ecd58eb7 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt @@ -73,6 +73,14 @@ internal enum class StorageScope { private val downloadsSharedDir: String? = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).canonicalPath private val documentsSharedDir: String? = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).canonicalPath + /** + * Determine if the given path is accessible. + */ + fun canAccess(path: String?): Boolean { + val storageScope = identifyStorageScope(path) + return storageScope == APP || storageScope == SHARED + } + /** * Determines which [StorageScope] the given path falls under. */ diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt index 5d57052ce6a..5d87f23b4a4 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt @@ -107,7 +107,7 @@ class FileAccessHandler(val context: Context) { } } - private val storageScopeIdentifier = StorageScope.Identifier(context) + internal val storageScopeIdentifier = StorageScope.Identifier(context) private val files = SparseArray() private var lastFileId = STARTING_FILE_ID diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 91bf7b48a6f..f1759af54a3 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -84,6 +84,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _dump_benchmark = p_env->GetMethodID(godot_class, "nativeDumpBenchmark", "(Ljava/lang/String;)V"); _get_gdextension_list_config_file = p_env->GetMethodID(godot_class, "getGDExtensionConfigFiles", "()[Ljava/lang/String;"); _has_feature = p_env->GetMethodID(godot_class, "hasFeature", "(Ljava/lang/String;)Z"); + _sign_apk = p_env->GetMethodID(godot_class, "nativeSignApk", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I"); + _verify_apk = p_env->GetMethodID(godot_class, "nativeVerifyApk", "(Ljava/lang/String;)I"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -424,3 +426,42 @@ bool GodotJavaWrapper::has_feature(const String &p_feature) const { return false; } } + +Error GodotJavaWrapper::sign_apk(const String &p_input_path, const String &p_output_path, const String &p_keystore_path, const String &p_keystore_user, const String &p_keystore_password) { + if (_sign_apk) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, ERR_UNCONFIGURED); + + jstring j_input_path = env->NewStringUTF(p_input_path.utf8().get_data()); + jstring j_output_path = env->NewStringUTF(p_output_path.utf8().get_data()); + jstring j_keystore_path = env->NewStringUTF(p_keystore_path.utf8().get_data()); + jstring j_keystore_user = env->NewStringUTF(p_keystore_user.utf8().get_data()); + jstring j_keystore_password = env->NewStringUTF(p_keystore_password.utf8().get_data()); + + int result = env->CallIntMethod(godot_instance, _sign_apk, j_input_path, j_output_path, j_keystore_path, j_keystore_user, j_keystore_password); + + env->DeleteLocalRef(j_input_path); + env->DeleteLocalRef(j_output_path); + env->DeleteLocalRef(j_keystore_path); + env->DeleteLocalRef(j_keystore_user); + env->DeleteLocalRef(j_keystore_password); + + return static_cast(result); + } else { + return ERR_UNCONFIGURED; + } +} + +Error GodotJavaWrapper::verify_apk(const String &p_apk_path) { + if (_verify_apk) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, ERR_UNCONFIGURED); + + jstring j_apk_path = env->NewStringUTF(p_apk_path.utf8().get_data()); + int result = env->CallIntMethod(godot_instance, _verify_apk, j_apk_path); + env->DeleteLocalRef(j_apk_path); + return static_cast(result); + } else { + return ERR_UNCONFIGURED; + } +} diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 358cf3261d7..6b665659811 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -75,6 +75,8 @@ private: jmethodID _end_benchmark_measure = nullptr; jmethodID _dump_benchmark = nullptr; jmethodID _has_feature = nullptr; + jmethodID _sign_apk = nullptr; + jmethodID _verify_apk = nullptr; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance); @@ -116,6 +118,10 @@ public: // Return true if the given feature is supported. bool has_feature(const String &p_feature) const; + + // Sign and verify apks + Error sign_apk(const String &p_input_path, const String &p_output_path, const String &p_keystore_path, const String &p_keystore_user, const String &p_keystore_password); + Error verify_apk(const String &p_apk_path); }; #endif // JAVA_GODOT_WRAPPER_H diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 764959eef35..7b0d3a29e90 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -775,6 +775,16 @@ void OS_Android::benchmark_dump() { #endif } +#ifdef TOOLS_ENABLED +Error OS_Android::sign_apk(const String &p_input_path, const String &p_output_path, const String &p_keystore_path, const String &p_keystore_user, const String &p_keystore_password) { + return godot_java->sign_apk(p_input_path, p_output_path, p_keystore_path, p_keystore_user, p_keystore_password); +} + +Error OS_Android::verify_apk(const String &p_apk_path) { + return godot_java->verify_apk(p_apk_path); +} +#endif + bool OS_Android::_check_internal_feature_support(const String &p_feature) { if (p_feature == "macos" || p_feature == "web_ios" || p_feature == "web_macos" || p_feature == "windows") { return false; diff --git a/platform/android/os_android.h b/platform/android/os_android.h index b150ef4f616..fb3cdf0d4c0 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -91,6 +91,11 @@ public: static const int DEFAULT_WINDOW_WIDTH = 800; static const int DEFAULT_WINDOW_HEIGHT = 600; +#ifdef TOOLS_ENABLED + Error sign_apk(const String &p_input_path, const String &p_output_path, const String &p_keystore_path, const String &p_keystore_user, const String &p_keystore_password); + Error verify_apk(const String &p_apk_path); +#endif + virtual void initialize_core() override; virtual void initialize() override; diff --git a/platform/web/export/export.cpp b/platform/web/export/export.cpp index 168310c0786..306ec624a05 100644 --- a/platform/web/export/export.cpp +++ b/platform/web/export/export.cpp @@ -40,7 +40,6 @@ void register_web_exporter_types() { } void register_web_exporter() { -#ifndef ANDROID_ENABLED EDITOR_DEF("export/web/http_host", "localhost"); EDITOR_DEF("export/web/http_port", 8060); EDITOR_DEF("export/web/use_tls", false); @@ -49,7 +48,6 @@ void register_web_exporter() { EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "export/web/http_port", PROPERTY_HINT_RANGE, "1,65535,1")); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/web/tls_key", PROPERTY_HINT_GLOBAL_FILE, "*.key")); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/web/tls_certificate", PROPERTY_HINT_GLOBAL_FILE, "*.crt,*.pem")); -#endif Ref platform; platform.instantiate();