summaryrefslogtreecommitdiffstats
path: root/platform
diff options
context:
space:
mode:
authorFredia Huya-Kouadio <fhuyakou@gmail.com>2024-06-16 12:14:34 -0700
committerFredia Huya-Kouadio <fhuyakou@gmail.com>2024-08-26 11:16:38 -0700
commita5897d579bb0af496a18f7430345a67fe27ff0df (patch)
tree6fee6886d95ce778af80ecd6b0467a4f86de9e06 /platform
parent794ea99240607a7afbc1512582083db82d81ff83 (diff)
downloadredot-engine-a5897d579bb0af496a18f7430345a67fe27ff0df.tar.gz
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.
Diffstat (limited to 'platform')
-rw-r--r--platform/android/dir_access_jandroid.cpp10
-rw-r--r--platform/android/dir_access_jandroid.h2
-rw-r--r--platform/android/export/export.cpp11
-rw-r--r--platform/android/export/export_plugin.cpp92
-rw-r--r--platform/android/file_access_android.h2
-rw-r--r--platform/android/file_access_filesystem_jandroid.h2
-rw-r--r--platform/android/java/THIRDPARTY.md (renamed from platform/android/java/lib/THIRDPARTY.md)28
-rw-r--r--platform/android/java/editor/src/main/assets/keystores/debug.keystorebin0 -> 2714 bytes
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt18
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java17
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotHost.java28
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt2
-rw-r--r--platform/android/java_godot_wrapper.cpp41
-rw-r--r--platform/android/java_godot_wrapper.h6
-rw-r--r--platform/android/os_android.cpp10
-rw-r--r--platform/android/os_android.h5
-rw-r--r--platform/web/export/export.cpp2
18 files changed, 228 insertions, 56 deletions
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index 56d41f2ab7..19c18eb96e 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 68578b0fa9..1d8fe906f3 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 6a6d7149ff..3f4624d09c 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 689360aef6..0fdaca4839 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 <string.h>
static const char *android_perms[] = {
@@ -2417,6 +2421,10 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
err += template_err;
}
} else {
+#ifdef ANDROID_ENABLED
+ err += TTR("Gradle build is not supported for the Android editor.") + "\n";
+ valid = false;
+#else
// Validate the custom gradle android source template.
bool android_source_template_valid = false;
const String android_source_template = p_preset->get("gradle_build/android_source_template");
@@ -2439,6 +2447,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
}
valid = installed_android_build_template && !r_missing_templates;
+#endif
}
// Validate the rest of the export configuration.
@@ -2475,6 +2484,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
err += TTR("Release keystore incorrectly configured in the export preset.") + "\n";
}
+#ifndef ANDROID_ENABLED
String java_sdk_path = EDITOR_GET("export/android/java_sdk_path");
if (java_sdk_path.is_empty()) {
err += TTR("A valid Java SDK path is required in Editor Settings.") + "\n";
@@ -2547,6 +2557,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
valid = false;
}
}
+#endif
if (!err.is_empty()) {
r_error = err;
@@ -2717,23 +2728,9 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP
Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &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 == "<FAILED>") {
- 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<EditorExportPreset> &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<EditorExportPreset> &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 == "<FAILED>") {
+ 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<String> args;
args.push_back("sign");
@@ -2778,7 +2805,7 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &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<EditorExportPreset> &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<EditorExportPreset> &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<EditorExportPreset> &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<EditorExportP
src_apk = find_export_template("android_release.apk");
}
if (src_apk.is_empty()) {
- add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Package not found: \"%s\"."), src_apk));
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("%s export template not found: \"%s\"."), (p_debug ? "Debug" : "Release"), src_apk));
return ERR_FILE_NOT_FOUND;
}
}
diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h
index e79daeafb3..b465a92c78 100644
--- a/platform/android/file_access_android.h
+++ b/platform/android/file_access_android.h
@@ -86,7 +86,7 @@ public:
virtual uint64_t _get_modified_time(const String &p_file) override { return 0; }
virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }
- virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return FAILED; }
+ virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> 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 6a8fc524b7..2795ac02ac 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<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }
- virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return FAILED; }
+ virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> 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
index 2496b59263..7807cc55ff 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 0000000000..3b7a97c8ee
--- /dev/null
+++ b/platform/android/java/editor/src/main/assets/keystores/debug.keystore
Binary files differ
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 1e0027089c..49e8ffb008 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 fdda766594..e0f5744368 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 1862b9fa9b..f1c84e90a7 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<GodotPlugin> 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 2e5649b563..574ecd58eb 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
@@ -74,6 +74,14 @@ internal enum class StorageScope {
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.
*/
fun identifyStorageScope(path: String?): StorageScope {
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 5d57052ce6..5d87f23b4a 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<DataAccess>()
private var lastFileId = STARTING_FILE_ID
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 91bf7b48a6..f1759af54a 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<Error>(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<Error>(result);
+ } else {
+ return ERR_UNCONFIGURED;
+ }
+}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 358cf3261d..6b66565981 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 764959eef3..7b0d3a29e9 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 b150ef4f61..fb3cdf0d4c 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 168310c078..306ec624a0 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<EditorExportPlatformWeb> platform;
platform.instantiate();