summaryrefslogtreecommitdiff
path: root/platform/android
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android')
-rw-r--r--platform/android/README.md14
-rw-r--r--platform/android/SCsub10
-rw-r--r--platform/android/android_input_handler.cpp395
-rw-r--r--platform/android/android_input_handler.h91
-rw-r--r--platform/android/android_keys_utils.cpp10
-rw-r--r--platform/android/android_keys_utils.h357
-rw-r--r--platform/android/api/api.cpp12
-rw-r--r--platform/android/api/api.h4
-rw-r--r--platform/android/api/java_class_wrapper.h19
-rw-r--r--platform/android/api/jni_singleton.h10
-rw-r--r--platform/android/audio_driver_jandroid.cpp185
-rw-r--r--platform/android/audio_driver_opensl.cpp6
-rw-r--r--platform/android/audio_driver_opensl.h21
-rw-r--r--platform/android/detect.py110
-rw-r--r--platform/android/dir_access_jandroid.cpp29
-rw-r--r--platform/android/dir_access_jandroid.h13
-rw-r--r--platform/android/display_server_android.cpp345
-rw-r--r--platform/android/display_server_android.h203
-rw-r--r--platform/android/export/export.cpp2754
-rw-r--r--platform/android/export/export.h4
-rw-r--r--platform/android/export/export_plugin.cpp2978
-rw-r--r--platform/android/export/export_plugin.h254
-rw-r--r--platform/android/export/godot_plugin_config.cpp211
-rw-r--r--platform/android/export/godot_plugin_config.h106
-rw-r--r--platform/android/export/gradle_export_util.cpp276
-rw-r--r--platform/android/export/gradle_export_util.h219
-rw-r--r--platform/android/file_access_android.cpp25
-rw-r--r--platform/android/file_access_android.h23
-rw-r--r--platform/android/file_access_jandroid.cpp197
-rw-r--r--platform/android/java/app/AndroidManifest.xml30
-rw-r--r--platform/android/java/app/assetPacks/installTime/build.gradle8
-rw-r--r--platform/android/java/app/assets/.gitignore2
-rw-r--r--platform/android/java/app/build.gradle76
-rw-r--r--platform/android/java/app/config.gradle165
-rw-r--r--platform/android/java/app/gradle.properties25
-rw-r--r--platform/android/java/app/res/drawable-nodpi/splash.png (renamed from platform/android/java/app/res/drawable/splash.png)bin14766 -> 14766 bytes
-rw-r--r--platform/android/java/app/res/drawable-nodpi/splash_bg_color.png (renamed from platform/android/java/app/res/drawable/splash_bg_color.png)bin1360 -> 1360 bytes
-rw-r--r--platform/android/java/app/res/drawable/splash_drawable.xml2
-rw-r--r--platform/android/java/app/res/values/themes.xml1
-rw-r--r--platform/android/java/app/settings.gradle4
-rw-r--r--platform/android/java/app/src/com/godot/game/GodotApp.java4
-rw-r--r--platform/android/java/build.gradle55
-rw-r--r--platform/android/java/gradle.properties14
-rw-r--r--platform/android/java/gradle/wrapper/gradle-wrapper.jarbin54329 -> 59203 bytes
-rw-r--r--platform/android/java/gradle/wrapper/gradle-wrapper.properties3
-rwxr-xr-xplatform/android/java/gradlew53
-rw-r--r--platform/android/java/gradlew.bat43
-rw-r--r--platform/android/java/lib/AndroidManifest.xml8
-rw-r--r--platform/android/java/lib/build.gradle67
-rw-r--r--platform/android/java/lib/res/values/strings.xml2
-rw-r--r--platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java2
-rw-r--r--platform/android/java/lib/src/com/google/android/vending/licensing/Obfuscator.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Dictionary.java20
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java89
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java387
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java26
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java80
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotHost.java67
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java372
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotInstrumentation.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotLib.java21
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java20
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java63
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java20
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java343
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java68
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java18
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java17
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java143
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java72
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java42
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/UsedByGodot.java45
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt12
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/XRMode.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java7
-rw-r--r--platform/android/java/nativeSrcsConfigs/AndroidManifest.xml2
-rw-r--r--platform/android/java/nativeSrcsConfigs/CMakeLists.txt (renamed from platform/android/java/lib/CMakeLists.txt)1
-rw-r--r--platform/android/java/nativeSrcsConfigs/README.md4
-rw-r--r--platform/android/java/nativeSrcsConfigs/build.gradle53
-rw-r--r--platform/android/java/settings.gradle4
-rw-r--r--platform/android/java_class_wrapper.cpp33
-rw-r--r--platform/android/java_godot_io_wrapper.cpp82
-rw-r--r--platform/android/java_godot_io_wrapper.h10
-rw-r--r--platform/android/java_godot_lib_jni.cpp205
-rw-r--r--platform/android/java_godot_lib_jni.h16
-rw-r--r--platform/android/java_godot_view_wrapper.cpp (renamed from platform/android/file_access_jandroid.h)86
-rw-r--r--platform/android/java_godot_view_wrapper.h (renamed from platform/android/audio_driver_jandroid.h)62
-rw-r--r--platform/android/java_godot_wrapper.cpp119
-rw-r--r--platform/android/java_godot_wrapper.h10
-rw-r--r--platform/android/jni_utils.cpp4
-rw-r--r--platform/android/jni_utils.h8
-rw-r--r--platform/android/logo.pngbin951 -> 968 bytes
-rw-r--r--platform/android/net_socket_android.cpp14
-rw-r--r--platform/android/net_socket_android.h8
-rw-r--r--platform/android/os_android.cpp98
-rw-r--r--platform/android/os_android.h58
-rw-r--r--platform/android/platform_config.h4
-rw-r--r--platform/android/plugin/godot_plugin_config.h268
-rw-r--r--platform/android/plugin/godot_plugin_jni.cpp24
-rw-r--r--platform/android/plugin/godot_plugin_jni.h14
-rw-r--r--platform/android/string_android.h10
-rw-r--r--platform/android/thread_jandroid.cpp130
-rw-r--r--platform/android/thread_jandroid.h46
-rw-r--r--platform/android/vulkan/vulkan_context_android.cpp27
-rw-r--r--platform/android/vulkan/vulkan_context_android.h15
121 files changed, 6969 insertions, 5914 deletions
diff --git a/platform/android/README.md b/platform/android/README.md
new file mode 100644
index 0000000000..343e588553
--- /dev/null
+++ b/platform/android/README.md
@@ -0,0 +1,14 @@
+# Android platform port
+
+This folder contains the Java and C++ (JNI) code for the Android platform port,
+using [Gradle](https://gradle.org/) as a build system.
+
+## Artwork license
+
+[`logo.png`](logo.png) and [`run_icon.png`](run_icon.png) are licensed under
+[Creative Commons Attribution 3.0 Unported](https://developer.android.com/distribute/marketing-tools/brand-guidelines#android_robot)
+per the Android logo usage guidelines:
+
+> The Android robot is reproduced or modified from work created and shared by
+> Google and used according to terms described in the Creative Commons 3.0
+> Attribution License.
diff --git a/platform/android/SCsub b/platform/android/SCsub
index ec42bc42b5..ecc72019e5 100644
--- a/platform/android/SCsub
+++ b/platform/android/SCsub
@@ -4,16 +4,16 @@ Import("env")
android_files = [
"os_android.cpp",
+ "android_input_handler.cpp",
"file_access_android.cpp",
"audio_driver_opensl.cpp",
- "file_access_jandroid.cpp",
"dir_access_jandroid.cpp",
"thread_jandroid.cpp",
"net_socket_android.cpp",
- "audio_driver_jandroid.cpp",
"java_godot_lib_jni.cpp",
"java_class_wrapper.cpp",
"java_godot_wrapper.cpp",
+ "java_godot_view_wrapper.cpp",
"java_godot_io_wrapper.cpp",
"jni_utils.cpp",
"android_keys_utils.cpp",
@@ -29,10 +29,14 @@ for x in android_files:
env_thirdparty = env_android.Clone()
env_thirdparty.disable_warnings()
-android_objects.append(env_thirdparty.SharedObject("#thirdparty/misc/ifaddrs-android.cc"))
+thirdparty_obj = env_thirdparty.SharedObject("#thirdparty/misc/ifaddrs-android.cc")
+android_objects.append(thirdparty_obj)
lib = env_android.add_shared_library("#bin/libgodot", [android_objects], SHLIBSUFFIX=env["SHLIBSUFFIX"])
+# Needed to force rebuilding the platform files when the thirdparty code is updated.
+env.Depends(lib, thirdparty_obj)
+
lib_arch_dir = ""
if env["android_arch"] == "armv7":
lib_arch_dir = "armeabi-v7a"
diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp
new file mode 100644
index 0000000000..dc259da886
--- /dev/null
+++ b/platform/android/android_input_handler.cpp
@@ -0,0 +1,395 @@
+/*************************************************************************/
+/* android_input_handler.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "android_input_handler.h"
+
+#include "android_keys_utils.h"
+#include "display_server_android.h"
+
+void AndroidInputHandler::process_joy_event(AndroidInputHandler::JoypadEvent p_event) {
+ switch (p_event.type) {
+ case JOY_EVENT_BUTTON:
+ Input::get_singleton()->joy_button(p_event.device, (JoyButton)p_event.index, p_event.pressed);
+ break;
+ case JOY_EVENT_AXIS:
+ Input::JoyAxisValue value;
+ value.min = -1;
+ value.value = p_event.value;
+ Input::get_singleton()->joy_axis(p_event.device, (JoyAxis)p_event.index, value);
+ break;
+ case JOY_EVENT_HAT:
+ Input::get_singleton()->joy_hat(p_event.device, p_event.hat);
+ break;
+ default:
+ return;
+ }
+}
+
+void AndroidInputHandler::_set_key_modifier_state(Ref<InputEventWithModifiers> ev) {
+ ev->set_shift_pressed(shift_mem);
+ ev->set_alt_pressed(alt_mem);
+ ev->set_meta_pressed(meta_mem);
+ ev->set_ctrl_pressed(control_mem);
+}
+
+void AndroidInputHandler::process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed) {
+ static char32_t prev_wc = 0;
+ char32_t unicode = p_unicode_char;
+ if ((p_unicode_char & 0xfffffc00) == 0xd800) {
+ if (prev_wc != 0) {
+ ERR_PRINT("invalid utf16 surrogate input");
+ }
+ prev_wc = unicode;
+ return; // Skip surrogate.
+ } else if ((unicode & 0xfffffc00) == 0xdc00) {
+ if (prev_wc == 0) {
+ ERR_PRINT("invalid utf16 surrogate input");
+ return; // Skip invalid surrogate.
+ }
+ unicode = (prev_wc << 10UL) + unicode - ((0xd800 << 10UL) + 0xdc00 - 0x10000);
+ prev_wc = 0;
+ } else {
+ prev_wc = 0;
+ }
+
+ Ref<InputEventKey> ev;
+ ev.instantiate();
+ int val = unicode;
+ Key keycode = android_get_keysym(p_keycode);
+ Key phy_keycode = android_get_keysym(p_scancode);
+
+ if (keycode == Key::SHIFT) {
+ shift_mem = p_pressed;
+ }
+ if (keycode == Key::ALT) {
+ alt_mem = p_pressed;
+ }
+ if (keycode == Key::CTRL) {
+ control_mem = p_pressed;
+ }
+ if (keycode == Key::META) {
+ meta_mem = p_pressed;
+ }
+
+ ev->set_keycode(keycode);
+ ev->set_physical_keycode(phy_keycode);
+ ev->set_unicode(val);
+ ev->set_pressed(p_pressed);
+
+ _set_key_modifier_state(ev);
+
+ if (val == '\n') {
+ ev->set_keycode(Key::ENTER);
+ } else if (val == 61448) {
+ ev->set_keycode(Key::BACKSPACE);
+ ev->set_unicode((char32_t)Key::BACKSPACE);
+ } else if (val == 61453) {
+ ev->set_keycode(Key::ENTER);
+ ev->set_unicode((char32_t)Key::ENTER);
+ } else if (p_keycode == 4) {
+ if (DisplayServerAndroid *dsa = Object::cast_to<DisplayServerAndroid>(DisplayServer::get_singleton())) {
+ dsa->send_window_event(DisplayServer::WINDOW_EVENT_GO_BACK_REQUEST, true);
+ }
+ }
+
+ Input::get_singleton()->parse_input_event(ev);
+}
+
+void AndroidInputHandler::process_touch(int p_event, int p_pointer, const Vector<AndroidInputHandler::TouchPos> &p_points) {
+ switch (p_event) {
+ case AMOTION_EVENT_ACTION_DOWN: { //gesture begin
+ if (touch.size()) {
+ //end all if exist
+ for (int i = 0; i < touch.size(); i++) {
+ Ref<InputEventScreenTouch> ev;
+ ev.instantiate();
+ ev->set_index(touch[i].id);
+ ev->set_pressed(false);
+ ev->set_position(touch[i].pos);
+ Input::get_singleton()->parse_input_event(ev);
+ }
+ }
+
+ touch.resize(p_points.size());
+ for (int i = 0; i < p_points.size(); i++) {
+ touch.write[i].id = p_points[i].id;
+ touch.write[i].pos = p_points[i].pos;
+ }
+
+ //send touch
+ for (int i = 0; i < touch.size(); i++) {
+ Ref<InputEventScreenTouch> ev;
+ ev.instantiate();
+ ev->set_index(touch[i].id);
+ ev->set_pressed(true);
+ ev->set_position(touch[i].pos);
+ Input::get_singleton()->parse_input_event(ev);
+ }
+
+ } break;
+ case AMOTION_EVENT_ACTION_MOVE: { //motion
+ ERR_FAIL_COND(touch.size() != p_points.size());
+
+ for (int i = 0; i < touch.size(); i++) {
+ int idx = -1;
+ for (int j = 0; j < p_points.size(); j++) {
+ if (touch[i].id == p_points[j].id) {
+ idx = j;
+ break;
+ }
+ }
+
+ ERR_CONTINUE(idx == -1);
+
+ if (touch[i].pos == p_points[idx].pos)
+ continue; //no move unncesearily
+
+ Ref<InputEventScreenDrag> ev;
+ ev.instantiate();
+ ev->set_index(touch[i].id);
+ ev->set_position(p_points[idx].pos);
+ ev->set_relative(p_points[idx].pos - touch[i].pos);
+ Input::get_singleton()->parse_input_event(ev);
+ touch.write[i].pos = p_points[idx].pos;
+ }
+
+ } break;
+ case AMOTION_EVENT_ACTION_CANCEL:
+ case AMOTION_EVENT_ACTION_UP: { //release
+ if (touch.size()) {
+ //end all if exist
+ for (int i = 0; i < touch.size(); i++) {
+ Ref<InputEventScreenTouch> ev;
+ ev.instantiate();
+ ev->set_index(touch[i].id);
+ ev->set_pressed(false);
+ ev->set_position(touch[i].pos);
+ Input::get_singleton()->parse_input_event(ev);
+ }
+ touch.clear();
+ }
+ } break;
+ case AMOTION_EVENT_ACTION_POINTER_DOWN: { // add touch
+ for (int i = 0; i < p_points.size(); i++) {
+ if (p_points[i].id == p_pointer) {
+ TouchPos tp = p_points[i];
+ touch.push_back(tp);
+
+ Ref<InputEventScreenTouch> ev;
+ ev.instantiate();
+
+ ev->set_index(tp.id);
+ ev->set_pressed(true);
+ ev->set_position(tp.pos);
+ Input::get_singleton()->parse_input_event(ev);
+
+ break;
+ }
+ }
+ } break;
+ case AMOTION_EVENT_ACTION_POINTER_UP: { // remove touch
+ for (int i = 0; i < touch.size(); i++) {
+ if (touch[i].id == p_pointer) {
+ Ref<InputEventScreenTouch> ev;
+ ev.instantiate();
+ ev->set_index(touch[i].id);
+ ev->set_pressed(false);
+ ev->set_position(touch[i].pos);
+ Input::get_singleton()->parse_input_event(ev);
+ touch.remove(i);
+
+ break;
+ }
+ }
+ } break;
+ }
+}
+
+void AndroidInputHandler::process_hover(int p_type, Point2 p_pos) {
+ // https://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER
+ switch (p_type) {
+ case AMOTION_EVENT_ACTION_HOVER_MOVE: // hover move
+ case AMOTION_EVENT_ACTION_HOVER_ENTER: // hover enter
+ case AMOTION_EVENT_ACTION_HOVER_EXIT: { // hover exit
+ Ref<InputEventMouseMotion> ev;
+ ev.instantiate();
+ _set_key_modifier_state(ev);
+ ev->set_position(p_pos);
+ ev->set_global_position(p_pos);
+ ev->set_relative(p_pos - hover_prev_pos);
+ Input::get_singleton()->parse_input_event(ev);
+ hover_prev_pos = p_pos;
+ } break;
+ }
+}
+
+void AndroidInputHandler::process_mouse_event(int input_device, int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor, float event_horizontal_factor) {
+ MouseButton event_buttons_mask = _android_button_mask_to_godot_button_mask(event_android_buttons_mask);
+ switch (event_action) {
+ case AMOTION_EVENT_ACTION_BUTTON_PRESS:
+ case AMOTION_EVENT_ACTION_BUTTON_RELEASE: {
+ Ref<InputEventMouseButton> ev;
+ ev.instantiate();
+ _set_key_modifier_state(ev);
+ if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) {
+ ev->set_position(event_pos);
+ ev->set_global_position(event_pos);
+ } else {
+ ev->set_position(hover_prev_pos);
+ ev->set_global_position(hover_prev_pos);
+ }
+ ev->set_pressed(event_action == AMOTION_EVENT_ACTION_BUTTON_PRESS);
+ MouseButton changed_button_mask = MouseButton(buttons_state ^ event_buttons_mask);
+
+ buttons_state = event_buttons_mask;
+
+ ev->set_button_index(_button_index_from_mask(changed_button_mask));
+ ev->set_button_mask(event_buttons_mask);
+ Input::get_singleton()->parse_input_event(ev);
+ } break;
+
+ case AMOTION_EVENT_ACTION_MOVE: {
+ Ref<InputEventMouseMotion> ev;
+ ev.instantiate();
+ _set_key_modifier_state(ev);
+ if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) {
+ ev->set_position(event_pos);
+ ev->set_global_position(event_pos);
+ ev->set_relative(event_pos - hover_prev_pos);
+ hover_prev_pos = event_pos;
+ } else {
+ ev->set_position(hover_prev_pos);
+ ev->set_global_position(hover_prev_pos);
+ ev->set_relative(event_pos);
+ }
+ ev->set_button_mask(event_buttons_mask);
+ Input::get_singleton()->parse_input_event(ev);
+ } break;
+ case AMOTION_EVENT_ACTION_SCROLL: {
+ Ref<InputEventMouseButton> ev;
+ ev.instantiate();
+ if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) {
+ ev->set_position(event_pos);
+ ev->set_global_position(event_pos);
+ } else {
+ ev->set_position(hover_prev_pos);
+ ev->set_global_position(hover_prev_pos);
+ }
+ ev->set_pressed(true);
+ buttons_state = event_buttons_mask;
+ if (event_vertical_factor > 0) {
+ _wheel_button_click(event_buttons_mask, ev, MouseButton::WHEEL_UP, event_vertical_factor);
+ } else if (event_vertical_factor < 0) {
+ _wheel_button_click(event_buttons_mask, ev, MouseButton::WHEEL_DOWN, -event_vertical_factor);
+ }
+
+ if (event_horizontal_factor > 0) {
+ _wheel_button_click(event_buttons_mask, ev, MouseButton::WHEEL_RIGHT, event_horizontal_factor);
+ } else if (event_horizontal_factor < 0) {
+ _wheel_button_click(event_buttons_mask, ev, MouseButton::WHEEL_LEFT, -event_horizontal_factor);
+ }
+ } break;
+ }
+}
+
+void AndroidInputHandler::_wheel_button_click(MouseButton event_buttons_mask, const Ref<InputEventMouseButton> &ev, MouseButton wheel_button, float factor) {
+ Ref<InputEventMouseButton> evd = ev->duplicate();
+ _set_key_modifier_state(evd);
+ evd->set_button_index(wheel_button);
+ evd->set_button_mask(MouseButton(event_buttons_mask ^ mouse_button_to_mask(wheel_button)));
+ evd->set_factor(factor);
+ Input::get_singleton()->parse_input_event(evd);
+ Ref<InputEventMouseButton> evdd = evd->duplicate();
+ evdd->set_pressed(false);
+ evdd->set_button_mask(event_buttons_mask);
+ Input::get_singleton()->parse_input_event(evdd);
+}
+
+void AndroidInputHandler::process_double_tap(int event_android_button_mask, Point2 p_pos) {
+ MouseButton event_button_mask = _android_button_mask_to_godot_button_mask(event_android_button_mask);
+ Ref<InputEventMouseButton> ev;
+ ev.instantiate();
+ _set_key_modifier_state(ev);
+ ev->set_position(p_pos);
+ ev->set_global_position(p_pos);
+ ev->set_pressed(event_button_mask != MouseButton::NONE);
+ ev->set_button_index(_button_index_from_mask(event_button_mask));
+ ev->set_button_mask(event_button_mask);
+ ev->set_double_click(true);
+ Input::get_singleton()->parse_input_event(ev);
+}
+
+MouseButton AndroidInputHandler::_button_index_from_mask(MouseButton button_mask) {
+ switch (button_mask) {
+ case MouseButton::MASK_LEFT:
+ return MouseButton::LEFT;
+ case MouseButton::MASK_RIGHT:
+ return MouseButton::RIGHT;
+ case MouseButton::MASK_MIDDLE:
+ return MouseButton::MIDDLE;
+ case MouseButton::MASK_XBUTTON1:
+ return MouseButton::MB_XBUTTON1;
+ case MouseButton::MASK_XBUTTON2:
+ return MouseButton::MB_XBUTTON2;
+ default:
+ return MouseButton::NONE;
+ }
+}
+
+MouseButton AndroidInputHandler::_android_button_mask_to_godot_button_mask(int android_button_mask) {
+ MouseButton godot_button_mask = MouseButton::NONE;
+ if (android_button_mask & AMOTION_EVENT_BUTTON_PRIMARY) {
+ godot_button_mask |= MouseButton::MASK_LEFT;
+ }
+ if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) {
+ godot_button_mask |= MouseButton::MASK_RIGHT;
+ }
+ if (android_button_mask & AMOTION_EVENT_BUTTON_TERTIARY) {
+ godot_button_mask |= MouseButton::MASK_MIDDLE;
+ }
+ if (android_button_mask & AMOTION_EVENT_BUTTON_BACK) {
+ godot_button_mask |= MouseButton::MASK_XBUTTON1;
+ }
+ if (android_button_mask & AMOTION_EVENT_BUTTON_FORWARD) {
+ godot_button_mask |= MouseButton::MASK_XBUTTON2;
+ }
+
+ return godot_button_mask;
+}
+
+void AndroidInputHandler::process_scroll(Point2 p_pos) {
+ Ref<InputEventPanGesture> ev;
+ ev.instantiate();
+ _set_key_modifier_state(ev);
+ ev->set_position(p_pos);
+ ev->set_delta(p_pos - scroll_prev_pos);
+ Input::get_singleton()->parse_input_event(ev);
+ scroll_prev_pos = p_pos;
+}
diff --git a/platform/android/android_input_handler.h b/platform/android/android_input_handler.h
new file mode 100644
index 0000000000..e0b4196f14
--- /dev/null
+++ b/platform/android/android_input_handler.h
@@ -0,0 +1,91 @@
+/*************************************************************************/
+/* android_input_handler.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef ANDROID_INPUT_HANDLER_H
+#define ANDROID_INPUT_HANDLER_H
+
+#include "core/input/input.h"
+
+// This class encapsulates all the handling of input events that come from the Android UI thread.
+// Remarks:
+// - It's not thread-safe by itself, so its functions must only be called on a single thread, which is the Android UI thread.
+// - Its functions must only call thread-safe methods.
+class AndroidInputHandler {
+public:
+ struct TouchPos {
+ int id = 0;
+ Point2 pos;
+ };
+
+ enum {
+ JOY_EVENT_BUTTON = 0,
+ JOY_EVENT_AXIS = 1,
+ JOY_EVENT_HAT = 2
+ };
+
+ struct JoypadEvent {
+ int device = 0;
+ int type = 0;
+ int index = 0; // Can be either JoyAxis or JoyButton.
+ bool pressed = false;
+ float value = 0;
+ HatMask hat = HatMask::CENTER;
+ };
+
+private:
+ bool alt_mem = false;
+ bool shift_mem = false;
+ bool control_mem = false;
+ bool meta_mem = false;
+
+ MouseButton buttons_state = MouseButton::NONE;
+
+ Vector<TouchPos> touch;
+ Point2 hover_prev_pos; // needed to calculate the relative position on hover events
+ Point2 scroll_prev_pos; // needed to calculate the relative position on scroll events
+
+ void _set_key_modifier_state(Ref<InputEventWithModifiers> ev);
+
+ static MouseButton _button_index_from_mask(MouseButton button_mask);
+ static MouseButton _android_button_mask_to_godot_button_mask(int android_button_mask);
+
+ void _wheel_button_click(MouseButton event_buttons_mask, const Ref<InputEventMouseButton> &ev, MouseButton wheel_button, float factor);
+
+public:
+ void process_touch(int p_event, int p_pointer, const Vector<TouchPos> &p_points);
+ void process_hover(int p_type, Point2 p_pos);
+ void process_mouse_event(int input_device, int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor = 0, float event_horizontal_factor = 0);
+ void process_double_tap(int event_android_button_mask, Point2 p_pos);
+ void process_scroll(Point2 p_pos);
+ void process_joy_event(JoypadEvent p_event);
+ void process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed);
+};
+
+#endif
diff --git a/platform/android/android_keys_utils.cpp b/platform/android/android_keys_utils.cpp
index b5b4fb9a4b..0cea0589ce 100644
--- a/platform/android/android_keys_utils.cpp
+++ b/platform/android/android_keys_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,12 +30,12 @@
#include "android_keys_utils.h"
-unsigned int android_get_keysym(unsigned int p_code) {
- for (int i = 0; _ak_to_keycode[i].keysym != KEY_UNKNOWN; i++) {
+Key android_get_keysym(unsigned int p_code) {
+ for (int i = 0; _ak_to_keycode[i].keysym != Key::UNKNOWN; i++) {
if (_ak_to_keycode[i].keycode == p_code) {
return _ak_to_keycode[i].keysym;
}
}
- return KEY_UNKNOWN;
+ return Key::UNKNOWN;
}
diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h
index 857bef02d1..bac9bab6db 100644
--- a/platform/android/android_keys_utils.h
+++ b/platform/android/android_keys_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,253 +31,132 @@
#ifndef ANDROID_KEYS_UTILS_H
#define ANDROID_KEYS_UTILS_H
+#include <android/input.h>
#include <core/os/keyboard.h>
-/*
- * Android Key codes.
- */
-enum {
- AKEYCODE_UNKNOWN = 0,
- AKEYCODE_SOFT_LEFT = 1,
- AKEYCODE_SOFT_RIGHT = 2,
- AKEYCODE_HOME = 3,
- AKEYCODE_BACK = 4,
- AKEYCODE_CALL = 5,
- AKEYCODE_ENDCALL = 6,
- AKEYCODE_0 = 7,
- AKEYCODE_1 = 8,
- AKEYCODE_2 = 9,
- AKEYCODE_3 = 10,
- AKEYCODE_4 = 11,
- AKEYCODE_5 = 12,
- AKEYCODE_6 = 13,
- AKEYCODE_7 = 14,
- AKEYCODE_8 = 15,
- AKEYCODE_9 = 16,
- AKEYCODE_STAR = 17,
- AKEYCODE_POUND = 18,
- AKEYCODE_DPAD_UP = 19,
- AKEYCODE_DPAD_DOWN = 20,
- AKEYCODE_DPAD_LEFT = 21,
- AKEYCODE_DPAD_RIGHT = 22,
- AKEYCODE_DPAD_CENTER = 23,
- AKEYCODE_VOLUME_UP = 24,
- AKEYCODE_VOLUME_DOWN = 25,
- AKEYCODE_POWER = 26,
- AKEYCODE_CAMERA = 27,
- AKEYCODE_CLEAR = 28,
- AKEYCODE_A = 29,
- AKEYCODE_B = 30,
- AKEYCODE_C = 31,
- AKEYCODE_D = 32,
- AKEYCODE_E = 33,
- AKEYCODE_F = 34,
- AKEYCODE_G = 35,
- AKEYCODE_H = 36,
- AKEYCODE_I = 37,
- AKEYCODE_J = 38,
- AKEYCODE_K = 39,
- AKEYCODE_L = 40,
- AKEYCODE_M = 41,
- AKEYCODE_N = 42,
- AKEYCODE_O = 43,
- AKEYCODE_P = 44,
- AKEYCODE_Q = 45,
- AKEYCODE_R = 46,
- AKEYCODE_S = 47,
- AKEYCODE_T = 48,
- AKEYCODE_U = 49,
- AKEYCODE_V = 50,
- AKEYCODE_W = 51,
- AKEYCODE_X = 52,
- AKEYCODE_Y = 53,
- AKEYCODE_Z = 54,
- AKEYCODE_COMMA = 55,
- AKEYCODE_PERIOD = 56,
- AKEYCODE_ALT_LEFT = 57,
- AKEYCODE_ALT_RIGHT = 58,
- AKEYCODE_SHIFT_LEFT = 59,
- AKEYCODE_SHIFT_RIGHT = 60,
- AKEYCODE_TAB = 61,
- AKEYCODE_SPACE = 62,
- AKEYCODE_SYM = 63,
- AKEYCODE_EXPLORER = 64,
- AKEYCODE_ENVELOPE = 65,
- AKEYCODE_ENTER = 66,
- AKEYCODE_DEL = 67,
- AKEYCODE_GRAVE = 68,
- AKEYCODE_MINUS = 69,
- AKEYCODE_EQUALS = 70,
- AKEYCODE_LEFT_BRACKET = 71,
- AKEYCODE_RIGHT_BRACKET = 72,
- AKEYCODE_BACKSLASH = 73,
- AKEYCODE_SEMICOLON = 74,
- AKEYCODE_APOSTROPHE = 75,
- AKEYCODE_SLASH = 76,
- AKEYCODE_AT = 77,
- AKEYCODE_NUM = 78,
- AKEYCODE_HEADSETHOOK = 79,
- AKEYCODE_FOCUS = 80, // *Camera* focus
- AKEYCODE_PLUS = 81,
- AKEYCODE_MENU = 82,
- AKEYCODE_NOTIFICATION = 83,
- AKEYCODE_SEARCH = 84,
- AKEYCODE_MEDIA_PLAY_PAUSE = 85,
- AKEYCODE_MEDIA_STOP = 86,
- AKEYCODE_MEDIA_NEXT = 87,
- AKEYCODE_MEDIA_PREVIOUS = 88,
- AKEYCODE_MEDIA_REWIND = 89,
- AKEYCODE_MEDIA_FAST_FORWARD = 90,
- AKEYCODE_MUTE = 91,
- AKEYCODE_PAGE_UP = 92,
- AKEYCODE_PAGE_DOWN = 93,
- AKEYCODE_PICTSYMBOLS = 94,
- AKEYCODE_SWITCH_CHARSET = 95,
- AKEYCODE_BUTTON_A = 96,
- AKEYCODE_BUTTON_B = 97,
- AKEYCODE_BUTTON_C = 98,
- AKEYCODE_BUTTON_X = 99,
- AKEYCODE_BUTTON_Y = 100,
- AKEYCODE_BUTTON_Z = 101,
- AKEYCODE_BUTTON_L1 = 102,
- AKEYCODE_BUTTON_R1 = 103,
- AKEYCODE_BUTTON_L2 = 104,
- AKEYCODE_BUTTON_R2 = 105,
- AKEYCODE_BUTTON_THUMBL = 106,
- AKEYCODE_BUTTON_THUMBR = 107,
- AKEYCODE_BUTTON_START = 108,
- AKEYCODE_BUTTON_SELECT = 109,
- AKEYCODE_BUTTON_MODE = 110,
- AKEYCODE_CONTROL_LEFT = 113,
- AKEYCODE_CONTROL_RIGHT = 114,
-
- // NOTE: If you add a new keycode here you must also add it to several other files.
- // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
-};
-
struct _WinTranslatePair {
- unsigned int keysym;
- unsigned int keycode;
+ Key keysym = Key::NONE;
+ unsigned int keycode = 0;
};
static _WinTranslatePair _ak_to_keycode[] = {
- { KEY_TAB, AKEYCODE_TAB },
- { KEY_ENTER, AKEYCODE_ENTER },
- { KEY_SHIFT, AKEYCODE_SHIFT_LEFT },
- { KEY_SHIFT, AKEYCODE_SHIFT_RIGHT },
- { KEY_ALT, AKEYCODE_ALT_LEFT },
- { KEY_ALT, AKEYCODE_ALT_RIGHT },
- { KEY_MENU, AKEYCODE_MENU },
- { KEY_PAUSE, AKEYCODE_MEDIA_PLAY_PAUSE },
- { KEY_ESCAPE, AKEYCODE_BACK },
- { KEY_SPACE, AKEYCODE_SPACE },
- { KEY_PAGEUP, AKEYCODE_PAGE_UP },
- { KEY_PAGEDOWN, AKEYCODE_PAGE_DOWN },
- { KEY_HOME, AKEYCODE_HOME }, //(0x24)
- { KEY_LEFT, AKEYCODE_DPAD_LEFT },
- { KEY_UP, AKEYCODE_DPAD_UP },
- { KEY_RIGHT, AKEYCODE_DPAD_RIGHT },
- { KEY_DOWN, AKEYCODE_DPAD_DOWN },
- { KEY_PERIODCENTERED, AKEYCODE_DPAD_CENTER },
- { KEY_BACKSPACE, AKEYCODE_DEL },
- { KEY_0, AKEYCODE_0 }, ////0 key
- { KEY_1, AKEYCODE_1 }, ////1 key
- { KEY_2, AKEYCODE_2 }, ////2 key
- { KEY_3, AKEYCODE_3 }, ////3 key
- { KEY_4, AKEYCODE_4 }, ////4 key
- { KEY_5, AKEYCODE_5 }, ////5 key
- { KEY_6, AKEYCODE_6 }, ////6 key
- { KEY_7, AKEYCODE_7 }, ////7 key
- { KEY_8, AKEYCODE_8 }, ////8 key
- { KEY_9, AKEYCODE_9 }, ////9 key
- { KEY_A, AKEYCODE_A }, ////A key
- { KEY_B, AKEYCODE_B }, ////B key
- { KEY_C, AKEYCODE_C }, ////C key
- { KEY_D, AKEYCODE_D }, ////D key
- { KEY_E, AKEYCODE_E }, ////E key
- { KEY_F, AKEYCODE_F }, ////F key
- { KEY_G, AKEYCODE_G }, ////G key
- { KEY_H, AKEYCODE_H }, ////H key
- { KEY_I, AKEYCODE_I }, ////I key
- { KEY_J, AKEYCODE_J }, ////J key
- { KEY_K, AKEYCODE_K }, ////K key
- { KEY_L, AKEYCODE_L }, ////L key
- { KEY_M, AKEYCODE_M }, ////M key
- { KEY_N, AKEYCODE_N }, ////N key
- { KEY_O, AKEYCODE_O }, ////O key
- { KEY_P, AKEYCODE_P }, ////P key
- { KEY_Q, AKEYCODE_Q }, ////Q key
- { KEY_R, AKEYCODE_R }, ////R key
- { KEY_S, AKEYCODE_S }, ////S key
- { KEY_T, AKEYCODE_T }, ////T key
- { KEY_U, AKEYCODE_U }, ////U key
- { KEY_V, AKEYCODE_V }, ////V key
- { KEY_W, AKEYCODE_W }, ////W key
- { KEY_X, AKEYCODE_X }, ////X key
- { KEY_Y, AKEYCODE_Y }, ////Y key
- { KEY_Z, AKEYCODE_Z }, ////Z key
- { KEY_HOMEPAGE, AKEYCODE_EXPLORER },
- { KEY_LAUNCH0, AKEYCODE_BUTTON_A },
- { KEY_LAUNCH1, AKEYCODE_BUTTON_B },
- { KEY_LAUNCH2, AKEYCODE_BUTTON_C },
- { KEY_LAUNCH3, AKEYCODE_BUTTON_X },
- { KEY_LAUNCH4, AKEYCODE_BUTTON_Y },
- { KEY_LAUNCH5, AKEYCODE_BUTTON_Z },
- { KEY_LAUNCH6, AKEYCODE_BUTTON_L1 },
- { KEY_LAUNCH7, AKEYCODE_BUTTON_R1 },
- { KEY_LAUNCH8, AKEYCODE_BUTTON_L2 },
- { KEY_LAUNCH9, AKEYCODE_BUTTON_R2 },
- { KEY_LAUNCHA, AKEYCODE_BUTTON_THUMBL },
- { KEY_LAUNCHB, AKEYCODE_BUTTON_THUMBR },
- { KEY_LAUNCHC, AKEYCODE_BUTTON_START },
- { KEY_LAUNCHD, AKEYCODE_BUTTON_SELECT },
- { KEY_LAUNCHE, AKEYCODE_BUTTON_MODE },
- { KEY_VOLUMEMUTE, AKEYCODE_MUTE },
- { KEY_VOLUMEDOWN, AKEYCODE_VOLUME_DOWN },
- { KEY_VOLUMEUP, AKEYCODE_VOLUME_UP },
- { KEY_BACK, AKEYCODE_MEDIA_REWIND },
- { KEY_FORWARD, AKEYCODE_MEDIA_FAST_FORWARD },
- { KEY_MEDIANEXT, AKEYCODE_MEDIA_NEXT },
- { KEY_MEDIAPREVIOUS, AKEYCODE_MEDIA_PREVIOUS },
- { KEY_MEDIASTOP, AKEYCODE_MEDIA_STOP },
- { KEY_PLUS, AKEYCODE_PLUS },
- { KEY_EQUAL, AKEYCODE_EQUALS }, // the '+' key
- { KEY_COMMA, AKEYCODE_COMMA }, // the ',' key
- { KEY_MINUS, AKEYCODE_MINUS }, // the '-' key
- { KEY_SLASH, AKEYCODE_SLASH }, // the '/?' key
- { KEY_BACKSLASH, AKEYCODE_BACKSLASH },
- { KEY_BRACKETLEFT, AKEYCODE_LEFT_BRACKET },
- { KEY_BRACKETRIGHT, AKEYCODE_RIGHT_BRACKET },
- { KEY_CONTROL, AKEYCODE_CONTROL_LEFT },
- { KEY_CONTROL, AKEYCODE_CONTROL_RIGHT },
- { KEY_UNKNOWN, 0 }
+ { Key::TAB, AKEYCODE_TAB },
+ { Key::ENTER, AKEYCODE_ENTER },
+ { Key::SHIFT, AKEYCODE_SHIFT_LEFT },
+ { Key::SHIFT, AKEYCODE_SHIFT_RIGHT },
+ { Key::ALT, AKEYCODE_ALT_LEFT },
+ { Key::ALT, AKEYCODE_ALT_RIGHT },
+ { Key::MENU, AKEYCODE_MENU },
+ { Key::PAUSE, AKEYCODE_MEDIA_PLAY_PAUSE },
+ { Key::ESCAPE, AKEYCODE_BACK },
+ { Key::SPACE, AKEYCODE_SPACE },
+ { Key::PAGEUP, AKEYCODE_PAGE_UP },
+ { Key::PAGEDOWN, AKEYCODE_PAGE_DOWN },
+ { Key::HOME, AKEYCODE_HOME }, //(0x24)
+ { Key::LEFT, AKEYCODE_DPAD_LEFT },
+ { Key::UP, AKEYCODE_DPAD_UP },
+ { Key::RIGHT, AKEYCODE_DPAD_RIGHT },
+ { Key::DOWN, AKEYCODE_DPAD_DOWN },
+ { Key::PERIODCENTERED, AKEYCODE_DPAD_CENTER },
+ { Key::BACKSPACE, AKEYCODE_DEL },
+ { Key::KEY_0, AKEYCODE_0 },
+ { Key::KEY_1, AKEYCODE_1 },
+ { Key::KEY_2, AKEYCODE_2 },
+ { Key::KEY_3, AKEYCODE_3 },
+ { Key::KEY_4, AKEYCODE_4 },
+ { Key::KEY_5, AKEYCODE_5 },
+ { Key::KEY_6, AKEYCODE_6 },
+ { Key::KEY_7, AKEYCODE_7 },
+ { Key::KEY_8, AKEYCODE_8 },
+ { Key::KEY_9, AKEYCODE_9 },
+ { Key::A, AKEYCODE_A },
+ { Key::B, AKEYCODE_B },
+ { Key::C, AKEYCODE_C },
+ { Key::D, AKEYCODE_D },
+ { Key::E, AKEYCODE_E },
+ { Key::F, AKEYCODE_F },
+ { Key::G, AKEYCODE_G },
+ { Key::H, AKEYCODE_H },
+ { Key::I, AKEYCODE_I },
+ { Key::J, AKEYCODE_J },
+ { Key::K, AKEYCODE_K },
+ { Key::L, AKEYCODE_L },
+ { Key::M, AKEYCODE_M },
+ { Key::N, AKEYCODE_N },
+ { Key::O, AKEYCODE_O },
+ { Key::P, AKEYCODE_P },
+ { Key::Q, AKEYCODE_Q },
+ { Key::R, AKEYCODE_R },
+ { Key::S, AKEYCODE_S },
+ { Key::T, AKEYCODE_T },
+ { Key::U, AKEYCODE_U },
+ { Key::V, AKEYCODE_V },
+ { Key::W, AKEYCODE_W },
+ { Key::X, AKEYCODE_X },
+ { Key::Y, AKEYCODE_Y },
+ { Key::Z, AKEYCODE_Z },
+ { Key::HOMEPAGE, AKEYCODE_EXPLORER },
+ { Key::LAUNCH0, AKEYCODE_BUTTON_A },
+ { Key::LAUNCH1, AKEYCODE_BUTTON_B },
+ { Key::LAUNCH2, AKEYCODE_BUTTON_C },
+ { Key::LAUNCH3, AKEYCODE_BUTTON_X },
+ { Key::LAUNCH4, AKEYCODE_BUTTON_Y },
+ { Key::LAUNCH5, AKEYCODE_BUTTON_Z },
+ { Key::LAUNCH6, AKEYCODE_BUTTON_L1 },
+ { Key::LAUNCH7, AKEYCODE_BUTTON_R1 },
+ { Key::LAUNCH8, AKEYCODE_BUTTON_L2 },
+ { Key::LAUNCH9, AKEYCODE_BUTTON_R2 },
+ { Key::LAUNCHA, AKEYCODE_BUTTON_THUMBL },
+ { Key::LAUNCHB, AKEYCODE_BUTTON_THUMBR },
+ { Key::LAUNCHC, AKEYCODE_BUTTON_START },
+ { Key::LAUNCHD, AKEYCODE_BUTTON_SELECT },
+ { Key::LAUNCHE, AKEYCODE_BUTTON_MODE },
+ { Key::VOLUMEMUTE, AKEYCODE_MUTE },
+ { Key::VOLUMEDOWN, AKEYCODE_VOLUME_DOWN },
+ { Key::VOLUMEUP, AKEYCODE_VOLUME_UP },
+ { Key::BACK, AKEYCODE_MEDIA_REWIND },
+ { Key::FORWARD, AKEYCODE_MEDIA_FAST_FORWARD },
+ { Key::MEDIANEXT, AKEYCODE_MEDIA_NEXT },
+ { Key::MEDIAPREVIOUS, AKEYCODE_MEDIA_PREVIOUS },
+ { Key::MEDIASTOP, AKEYCODE_MEDIA_STOP },
+ { Key::PLUS, AKEYCODE_PLUS },
+ { Key::EQUAL, AKEYCODE_EQUALS }, // the '+' key
+ { Key::COMMA, AKEYCODE_COMMA }, // the ',' key
+ { Key::MINUS, AKEYCODE_MINUS }, // the '-' key
+ { Key::SLASH, AKEYCODE_SLASH }, // the '/?' key
+ { Key::BACKSLASH, AKEYCODE_BACKSLASH },
+ { Key::BRACKETLEFT, AKEYCODE_LEFT_BRACKET },
+ { Key::BRACKETRIGHT, AKEYCODE_RIGHT_BRACKET },
+ { Key::CTRL, AKEYCODE_CTRL_LEFT },
+ { Key::CTRL, AKEYCODE_CTRL_RIGHT },
+ { Key::UNKNOWN, 0 }
};
/*
TODO: map these android key:
- AKEYCODE_SOFT_LEFT = 1,
- AKEYCODE_SOFT_RIGHT = 2,
- AKEYCODE_CALL = 5,
- AKEYCODE_ENDCALL = 6,
- AKEYCODE_STAR = 17,
- AKEYCODE_POUND = 18,
- AKEYCODE_POWER = 26,
- AKEYCODE_CAMERA = 27,
- AKEYCODE_CLEAR = 28,
- AKEYCODE_SYM = 63,
- AKEYCODE_ENVELOPE = 65,
- AKEYCODE_GRAVE = 68,
- AKEYCODE_SEMICOLON = 74,
- AKEYCODE_APOSTROPHE = 75,
- AKEYCODE_AT = 77,
- AKEYCODE_NUM = 78,
- AKEYCODE_HEADSETHOOK = 79,
- AKEYCODE_FOCUS = 80, // *Camera* focus
- AKEYCODE_NOTIFICATION = 83,
- AKEYCODE_SEARCH = 84,
- AKEYCODE_PICTSYMBOLS = 94,
- AKEYCODE_SWITCH_CHARSET = 95,
+ AKEYCODE_SOFT_LEFT = 1,
+ AKEYCODE_SOFT_RIGHT = 2,
+ AKEYCODE_CALL = 5,
+ AKEYCODE_ENDCALL = 6,
+ AKEYCODE_STAR = 17,
+ AKEYCODE_POUND = 18,
+ AKEYCODE_POWER = 26,
+ AKEYCODE_CAMERA = 27,
+ AKEYCODE_CLEAR = 28,
+ AKEYCODE_SYM = 63,
+ AKEYCODE_ENVELOPE = 65,
+ AKEYCODE_GRAVE = 68,
+ AKEYCODE_SEMICOLON = 74,
+ AKEYCODE_APOSTROPHE = 75,
+ AKEYCODE_AT = 77,
+ AKEYCODE_NUM = 78,
+ AKEYCODE_HEADSETHOOK = 79,
+ AKEYCODE_FOCUS = 80, // *Camera* focus
+ AKEYCODE_NOTIFICATION = 83,
+ AKEYCODE_SEARCH = 84,
+ AKEYCODE_PICTSYMBOLS = 94,
+ AKEYCODE_SWITCH_CHARSET = 95,
*/
-unsigned int android_get_keysym(unsigned int p_code);
+Key android_get_keysym(unsigned int p_code);
#endif // ANDROID_KEYS_UTILS_H
diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp
index 1f140f7119..03355e4815 100644
--- a/platform/android/api/api.cpp
+++ b/platform/android/api/api.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,7 +30,7 @@
#include "api.h"
-#include "core/engine.h"
+#include "core/config/engine.h"
#include "java_class_wrapper.h"
#include "jni_singleton.h"
@@ -44,11 +44,11 @@ void register_android_api() {
// `JNISingleton` registration occurs in
// `platform/android/java_godot_lib_jni.cpp#Java_org_godotengine_godot_GodotLib_setup`
java_class_wrapper = memnew(JavaClassWrapper); // Dummy
- ClassDB::register_class<JNISingleton>();
+ GDREGISTER_CLASS(JNISingleton);
#endif
- ClassDB::register_class<JavaClass>();
- ClassDB::register_class<JavaClassWrapper>();
+ GDREGISTER_CLASS(JavaClass);
+ GDREGISTER_CLASS(JavaClassWrapper);
Engine::get_singleton()->add_singleton(Engine::Singleton("JavaClassWrapper", JavaClassWrapper::get_singleton()));
}
diff --git a/platform/android/api/api.h b/platform/android/api/api.h
index 5e951b9c88..fe3a6734ac 100644
--- a/platform/android/api/api.h
+++ b/platform/android/api/api.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h
index 1fa2726784..ff7bf43573 100644
--- a/platform/android/api/java_class_wrapper.h
+++ b/platform/android/api/java_class_wrapper.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,7 +31,7 @@
#ifndef JAVA_CLASS_WRAPPER_H
#define JAVA_CLASS_WRAPPER_H
-#include "core/reference.h"
+#include "core/object/ref_counted.h"
#ifdef ANDROID_ENABLED
#include <android/log.h>
@@ -42,12 +42,11 @@
class JavaObject;
#endif
-class JavaClass : public Reference {
- GDCLASS(JavaClass, Reference);
+class JavaClass : public RefCounted {
+ GDCLASS(JavaClass, RefCounted);
#ifdef ANDROID_ENABLED
enum ArgumentType{
-
ARG_TYPE_VOID,
ARG_TYPE_BOOLEAN,
ARG_TYPE_BYTE,
@@ -67,10 +66,10 @@ class JavaClass : public Reference {
Map<StringName, Variant> constant_map;
struct MethodInfo {
- bool _static;
+ bool _static = false;
Vector<uint32_t> param_types;
Vector<StringName> param_sigs;
- uint32_t return_type;
+ uint32_t return_type = 0;
jmethodID method;
};
@@ -185,8 +184,8 @@ public:
JavaClass();
};
-class JavaObject : public Reference {
- GDCLASS(JavaObject, Reference);
+class JavaObject : public RefCounted {
+ GDCLASS(JavaObject, RefCounted);
#ifdef ANDROID_ENABLED
Ref<JavaClass> base_class;
diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h
index 5e63f20d6c..965eaabf81 100644
--- a/platform/android/api/jni_singleton.h
+++ b/platform/android/api/jni_singleton.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,8 +31,8 @@
#ifndef JNI_SINGLETON_H
#define JNI_SINGLETON_H
-#include <core/engine.h>
-#include <core/variant.h>
+#include <core/config/engine.h>
+#include <core/variant/variant.h>
#ifdef ANDROID_ENABLED
#include <platform/android/jni_utils.h>
#endif
@@ -83,7 +83,7 @@ public:
v = (jvalue *)alloca(sizeof(jvalue) * p_argcount);
}
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
int res = env->PushLocalFrame(16);
diff --git a/platform/android/audio_driver_jandroid.cpp b/platform/android/audio_driver_jandroid.cpp
deleted file mode 100644
index 09c981b3fa..0000000000
--- a/platform/android/audio_driver_jandroid.cpp
+++ /dev/null
@@ -1,185 +0,0 @@
-/*************************************************************************/
-/* audio_driver_jandroid.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "audio_driver_jandroid.h"
-
-#include "core/os/os.h"
-#include "core/project_settings.h"
-#include "thread_jandroid.h"
-
-AudioDriverAndroid *AudioDriverAndroid::s_ad = nullptr;
-
-jobject AudioDriverAndroid::io;
-jmethodID AudioDriverAndroid::_init_audio;
-jmethodID AudioDriverAndroid::_write_buffer;
-jmethodID AudioDriverAndroid::_quit;
-jmethodID AudioDriverAndroid::_pause;
-bool AudioDriverAndroid::active = false;
-jclass AudioDriverAndroid::cls;
-int AudioDriverAndroid::audioBufferFrames = 0;
-int AudioDriverAndroid::mix_rate = 44100;
-bool AudioDriverAndroid::quit = false;
-jobject AudioDriverAndroid::audioBuffer = nullptr;
-void *AudioDriverAndroid::audioBufferPinned = nullptr;
-Mutex AudioDriverAndroid::mutex;
-int32_t *AudioDriverAndroid::audioBuffer32 = nullptr;
-
-const char *AudioDriverAndroid::get_name() const {
- return "Android";
-}
-
-Error AudioDriverAndroid::init() {
- /*
- // TODO: pass in/return a (Java) device ID, also whether we're opening for input or output
- this->spec.samples = Android_JNI_OpenAudioDevice(this->spec.freq, this->spec.format == AUDIO_U8 ? 0 : 1, this->spec.channels, this->spec.samples);
- SDL_CalculateAudioSpec(&this->spec);
-
- if (this->spec.samples == 0) {
- // Init failed?
- SDL_SetError("Java-side initialization failed!");
- return 0;
- }
-*/
-
- //Android_JNI_SetupThread();
-
- // __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
-
- JNIEnv *env = ThreadAndroid::get_env();
- int mix_rate = GLOBAL_GET("audio/mix_rate");
-
- int latency = GLOBAL_GET("audio/output_latency");
- unsigned int buffer_size = next_power_of_2(latency * mix_rate / 1000);
- print_verbose("Audio buffer size: " + itos(buffer_size));
-
- audioBuffer = env->CallObjectMethod(io, _init_audio, mix_rate, buffer_size);
-
- ERR_FAIL_COND_V(audioBuffer == nullptr, ERR_INVALID_PARAMETER);
-
- audioBuffer = env->NewGlobalRef(audioBuffer);
-
- jboolean isCopy = JNI_FALSE;
- audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
- audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
- audioBuffer32 = memnew_arr(int32_t, audioBufferFrames);
-
- return OK;
-}
-
-void AudioDriverAndroid::start() {
- active = true;
-}
-
-void AudioDriverAndroid::setup(jobject p_io) {
- JNIEnv *env = ThreadAndroid::get_env();
- io = p_io;
-
- jclass c = env->GetObjectClass(io);
- cls = (jclass)env->NewGlobalRef(c);
-
- _init_audio = env->GetMethodID(cls, "audioInit", "(II)Ljava/lang/Object;");
- _write_buffer = env->GetMethodID(cls, "audioWriteShortBuffer", "([S)V");
- _quit = env->GetMethodID(cls, "audioQuit", "()V");
- _pause = env->GetMethodID(cls, "audioPause", "(Z)V");
-}
-
-void AudioDriverAndroid::thread_func(JNIEnv *env) {
- jclass cls = env->FindClass("org/godotengine/godot/Godot");
- if (cls) {
- cls = (jclass)env->NewGlobalRef(cls);
- }
- jfieldID fid = env->GetStaticFieldID(cls, "io", "Lorg/godotengine/godot/GodotIO;");
- jobject ob = env->GetStaticObjectField(cls, fid);
- jobject gob = env->NewGlobalRef(ob);
- jclass c = env->GetObjectClass(gob);
- jclass lcls = (jclass)env->NewGlobalRef(c);
- _write_buffer = env->GetMethodID(lcls, "audioWriteShortBuffer", "([S)V");
-
- while (!quit) {
- int16_t *ptr = (int16_t *)audioBufferPinned;
- int fc = audioBufferFrames;
-
- if (!s_ad->active || mutex.try_lock() != OK) {
- for (int i = 0; i < fc; i++) {
- ptr[i] = 0;
- }
-
- } else {
- s_ad->audio_server_process(fc / 2, audioBuffer32);
-
- mutex.unlock();
-
- for (int i = 0; i < fc; i++) {
- ptr[i] = audioBuffer32[i] >> 16;
- }
- }
- env->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)ptr, JNI_COMMIT);
- env->CallVoidMethod(gob, _write_buffer, (jshortArray)audioBuffer);
- }
-}
-
-int AudioDriverAndroid::get_mix_rate() const {
- return mix_rate;
-}
-
-AudioDriver::SpeakerMode AudioDriverAndroid::get_speaker_mode() const {
- return SPEAKER_MODE_STEREO;
-}
-
-void AudioDriverAndroid::lock() {
- mutex.lock();
-}
-
-void AudioDriverAndroid::unlock() {
- mutex.unlock();
-}
-
-void AudioDriverAndroid::finish() {
- JNIEnv *env = ThreadAndroid::get_env();
- env->CallVoidMethod(io, _quit);
-
- if (audioBuffer) {
- env->DeleteGlobalRef(audioBuffer);
- audioBuffer = nullptr;
- audioBufferPinned = nullptr;
- }
-
- active = false;
-}
-
-void AudioDriverAndroid::set_pause(bool p_pause) {
- JNIEnv *env = ThreadAndroid::get_env();
- env->CallVoidMethod(io, _pause, p_pause);
-}
-
-AudioDriverAndroid::AudioDriverAndroid() {
- s_ad = this;
- active = false;
-}
diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp
index 740e9a3132..a1d8fb4810 100644
--- a/platform/android/audio_driver_opensl.cpp
+++ b/platform/android/audio_driver_opensl.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -339,6 +339,4 @@ void AudioDriverOpenSL::set_pause(bool p_pause) {
AudioDriverOpenSL::AudioDriverOpenSL() {
s_ad = this;
- pause = false;
- active = false;
}
diff --git a/platform/android/audio_driver_opensl.h b/platform/android/audio_driver_opensl.h
index 9858a40822..fcc2513f3f 100644
--- a/platform/android/audio_driver_opensl.h
+++ b/platform/android/audio_driver_opensl.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -38,20 +38,19 @@
#include <SLES/OpenSLES_Android.h>
class AudioDriverOpenSL : public AudioDriver {
- bool active;
+ bool active = false;
Mutex mutex;
enum {
-
BUFFER_COUNT = 2
};
- bool pause;
+ bool pause = false;
- uint32_t buffer_size;
- int16_t *buffers[BUFFER_COUNT];
- int32_t *mixdown_buffer;
- int last_free;
+ uint32_t buffer_size = 0;
+ int16_t *buffers[BUFFER_COUNT] = {};
+ int32_t *mixdown_buffer = nullptr;
+ int last_free = 0;
Vector<int16_t> rec_buffer;
@@ -60,7 +59,6 @@ class AudioDriverOpenSL : public AudioDriver {
SLObjectItf sl;
SLEngineItf EngineItf;
SLObjectItf OutputMix;
- SLVolumeItf volumeItf;
SLObjectItf player;
SLObjectItf recorder;
SLAndroidSimpleBufferQueueItf bufferQueueItf;
@@ -69,7 +67,6 @@ class AudioDriverOpenSL : public AudioDriver {
SLDataFormat_PCM pcm;
SLDataSink audioSink;
SLDataLocator_OutputMix locator_outputmix;
- SLBufferQueueState state;
static AudioDriverOpenSL *s_ad;
@@ -90,8 +87,6 @@ class AudioDriverOpenSL : public AudioDriver {
virtual Error capture_init_device();
public:
- void set_singleton();
-
virtual const char *get_name() const;
virtual Error init();
diff --git a/platform/android/detect.py b/platform/android/detect.py
index 0accacb679..6f98dab2cc 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -13,7 +13,7 @@ def get_name():
def can_build():
- return "ANDROID_NDK_ROOT" in os.environ
+ return ("ANDROID_SDK_ROOT" in os.environ) or ("ANDROID_HOME" in os.environ)
def get_platform(platform):
@@ -24,13 +24,32 @@ def get_opts():
from SCons.Variables import BoolVariable, EnumVariable
return [
- ("ANDROID_NDK_ROOT", "Path to the Android NDK", os.environ.get("ANDROID_NDK_ROOT", 0)),
+ ("ANDROID_NDK_ROOT", "Path to the Android NDK", get_android_ndk_root()),
+ ("ANDROID_SDK_ROOT", "Path to the Android SDK", get_android_sdk_root()),
("ndk_platform", 'Target platform (android-<api>, e.g. "android-24")', "android-24"),
- EnumVariable("android_arch", "Target architecture", "armv7", ("armv7", "arm64v8", "x86", "x86_64")),
- BoolVariable("android_neon", "Enable NEON support (armv7 only)", True),
+ EnumVariable("android_arch", "Target architecture", "arm64v8", ("armv7", "arm64v8", "x86", "x86_64")),
]
+# Return the ANDROID_SDK_ROOT environment variable.
+# While ANDROID_HOME has been deprecated, it's used as a fallback for backward
+# compatibility purposes.
+def get_android_sdk_root():
+ if "ANDROID_SDK_ROOT" in os.environ:
+ return os.environ.get("ANDROID_SDK_ROOT", 0)
+ else:
+ return os.environ.get("ANDROID_HOME", 0)
+
+
+# Return the ANDROID_NDK_ROOT environment variable.
+# We generate one for this build using the ANDROID_SDK_ROOT env
+# variable and the project ndk version.
+# If the env variable is already defined, we override it with
+# our own to match what the project expects.
+def get_android_ndk_root():
+ return get_android_sdk_root() + "/ndk/" + get_project_ndk_version()
+
+
def get_flags():
return [
("tools", False),
@@ -47,9 +66,33 @@ def create(env):
return env.Clone(tools=tools)
+# Check if ANDROID_NDK_ROOT is valid.
+# If not, install the ndk using ANDROID_SDK_ROOT and sdkmanager.
+def install_ndk_if_needed(env):
+ print("Checking for Android NDK...")
+ env_ndk_version = get_env_ndk_version(env["ANDROID_NDK_ROOT"])
+ if env_ndk_version is None:
+ # Reinstall the ndk and update ANDROID_NDK_ROOT.
+ print("Installing Android NDK...")
+ if env["ANDROID_SDK_ROOT"] is None:
+ raise Exception("Invalid ANDROID_SDK_ROOT environment variable.")
+
+ import subprocess
+
+ extension = ".bat" if os.name == "nt" else ""
+ sdkmanager_path = env["ANDROID_SDK_ROOT"] + "/cmdline-tools/latest/bin/sdkmanager" + extension
+ ndk_download_args = "ndk;" + get_project_ndk_version()
+ subprocess.check_call([sdkmanager_path, ndk_download_args])
+
+ env["ANDROID_NDK_ROOT"] = env["ANDROID_SDK_ROOT"] + "/ndk/" + get_project_ndk_version()
+ print("ANDROID_NDK_ROOT: " + env["ANDROID_NDK_ROOT"])
+
+
def configure(env):
+ install_ndk_if_needed(env)
+
# Workaround for MinGW. See:
- # http://www.scons.org/wiki/LongCmdLinesOnWin32
+ # https://www.scons.org/wiki/LongCmdLinesOnWin32
if os.name == "nt":
import subprocess
@@ -99,10 +142,7 @@ def configure(env):
if env["android_arch"] not in ["armv7", "arm64v8", "x86", "x86_64"]:
env["android_arch"] = "armv7"
- neon_text = ""
- if env["android_arch"] == "armv7" and env["android_neon"]:
- neon_text = " (with NEON)"
- print("Building for Android, platform " + env["ndk_platform"] + " (" + env["android_arch"] + ")" + neon_text)
+ print("Building for Android, platform " + env["ndk_platform"] + " (" + env["android_arch"] + ")")
can_vectorize = True
if env["android_arch"] == "x86":
@@ -130,10 +170,7 @@ def configure(env):
target_subpath = "arm-linux-androideabi-4.9"
abi_subpath = "arm-linux-androideabi"
arch_subpath = "armeabi-v7a"
- if env["android_neon"]:
- env.extra_suffix = ".armv7.neon" + env.extra_suffix
- else:
- env.extra_suffix = ".armv7" + env.extra_suffix
+ env.extra_suffix = ".armv7" + env.extra_suffix
elif env["android_arch"] == "arm64v8":
if get_platform(env["ndk_platform"]) < 21:
print(
@@ -153,20 +190,17 @@ def configure(env):
if env["optimize"] == "speed": # optimize for speed (default)
env.Append(LINKFLAGS=["-O2"])
env.Append(CCFLAGS=["-O2", "-fomit-frame-pointer"])
- env.Append(CPPDEFINES=["NDEBUG"])
- else: # optimize for size
+ elif env["optimize"] == "size": # optimize for size
env.Append(CCFLAGS=["-Os"])
- env.Append(CPPDEFINES=["NDEBUG"])
env.Append(LINKFLAGS=["-Os"])
+ env.Append(CPPDEFINES=["NDEBUG"])
if can_vectorize:
env.Append(CCFLAGS=["-ftree-vectorize"])
- if env["target"] == "release_debug":
- env.Append(CPPDEFINES=["DEBUG_ENABLED"])
elif env["target"] == "debug":
env.Append(LINKFLAGS=["-O0"])
env.Append(CCFLAGS=["-O0", "-g", "-fno-limit-debug-info"])
- env.Append(CPPDEFINES=["_DEBUG", "DEBUG_ENABLED"])
+ env.Append(CPPDEFINES=["_DEBUG"])
env.Append(CPPFLAGS=["-UNDEBUG"])
# Compiler configuration
@@ -207,7 +241,7 @@ def configure(env):
env["RANLIB"] = tools_path + "/ranlib"
env["AS"] = tools_path + "/as"
- common_opts = ["-fno-integrated-as", "-gcc-toolchain", gcc_toolchain_path]
+ common_opts = ["-gcc-toolchain", gcc_toolchain_path]
# Compile flags
@@ -217,6 +251,8 @@ def configure(env):
# Disable exceptions and rtti on non-tools (template) builds
if env["tools"]:
env.Append(CXXFLAGS=["-frtti"])
+ elif env["builtin_icu"]:
+ env.Append(CXXFLAGS=["-frtti", "-fno-exceptions"])
else:
env.Append(CXXFLAGS=["-fno-rtti", "-fno-exceptions"])
# Don't use dynamic_cast, necessary with no-rtti.
@@ -240,7 +276,9 @@ def configure(env):
)
env.Append(CPPDEFINES=["NO_STATVFS", "GLES_ENABLED"])
- env["neon_enabled"] = False
+ if get_platform(env["ndk_platform"]) >= 24:
+ env.Append(CPPDEFINES=[("_FILE_OFFSET_BITS", 64)])
+
if env["android_arch"] == "x86":
target_opts = ["-target", "i686-none-linux-android"]
# The NDK adds this if targeting API < 21, so we can drop it when Godot targets it at least
@@ -253,12 +291,9 @@ def configure(env):
target_opts = ["-target", "armv7-none-linux-androideabi"]
env.Append(CCFLAGS="-march=armv7-a -mfloat-abi=softfp".split())
env.Append(CPPDEFINES=["__ARM_ARCH_7__", "__ARM_ARCH_7A__"])
- if env["android_neon"]:
- env["neon_enabled"] = True
- env.Append(CCFLAGS=["-mfpu=neon"])
- env.Append(CPPDEFINES=["__ARM_NEON__"])
- else:
- env.Append(CCFLAGS=["-mfpu=vfpv3-d16"])
+ # Enable ARM NEON instructions to compile more optimized code.
+ env.Append(CCFLAGS=["-mfpu=neon"])
+ env.Append(CPPDEFINES=["__ARM_NEON__"])
elif env["android_arch"] == "arm64v8":
target_opts = ["-target", "aarch64-none-linux-android"]
@@ -270,7 +305,7 @@ def configure(env):
# Link flags
- ndk_version = get_ndk_version(env["ANDROID_NDK_ROOT"])
+ ndk_version = get_env_ndk_version(env["ANDROID_NDK_ROOT"])
if ndk_version != None and LooseVersion(ndk_version) >= LooseVersion("17.1.4828580"):
env.Append(LINKFLAGS=["-Wl,--exclude-libs,libgcc.a", "-Wl,--exclude-libs,libatomic.a", "-nostdlib++"])
else:
@@ -319,12 +354,23 @@ def configure(env):
)
env.Prepend(CPPPATH=["#platform/android"])
- env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED", "VULKAN_ENABLED", "NO_FCNTL"])
- env.Append(LIBS=["OpenSLES", "EGL", "GLESv2", "vulkan", "android", "log", "z", "dl"])
+ env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED", "NO_FCNTL"])
+ env.Append(LIBS=["OpenSLES", "EGL", "GLESv2", "android", "log", "z", "dl"])
+
+ if env["vulkan"]:
+ env.Append(CPPDEFINES=["VULKAN_ENABLED"])
+ if not env["use_volk"]:
+ env.Append(LIBS=["vulkan"])
+
+
+# Return the project NDK version.
+# This is kept in sync with the value in 'platform/android/java/app/config.gradle'.
+def get_project_ndk_version():
+ return "21.4.7075529"
# Return NDK version string in source.properties (adapted from the Chromium project).
-def get_ndk_version(path):
+def get_env_ndk_version(path):
if path is None:
return None
prop_file_path = os.path.join(path, "source.properties")
@@ -334,6 +380,6 @@ def get_ndk_version(path):
key_value = list(map(lambda x: x.strip(), line.split("=")))
if key_value[0] == "Pkg.Revision":
return key_value[1]
- except:
+ except Exception:
print("Could not read source prop file '%s'" % prop_file_path)
return None
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index ca312b427f..0eeee8215d 100644
--- a/platform/android/dir_access_jandroid.cpp
+++ b/platform/android/dir_access_jandroid.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -29,8 +29,8 @@
/*************************************************************************/
#include "dir_access_jandroid.h"
-#include "core/print_string.h"
-#include "file_access_jandroid.h"
+#include "core/string/print_string.h"
+#include "file_access_android.h"
#include "string_android.h"
#include "thread_jandroid.h"
@@ -47,7 +47,7 @@ DirAccess *DirAccessJAndroid::create_fs() {
Error DirAccessJAndroid::list_dir_begin() {
list_dir_end();
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
jstring js = env->NewStringUTF(current_dir.utf8().get_data());
int res = env->CallIntMethod(io, _dir_open, js);
@@ -62,7 +62,7 @@ Error DirAccessJAndroid::list_dir_begin() {
String DirAccessJAndroid::get_next() {
ERR_FAIL_COND_V(id == 0, "");
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
jstring str = (jstring)env->CallObjectMethod(io, _dir_next, id);
if (!str)
return "";
@@ -73,7 +73,7 @@ String DirAccessJAndroid::get_next() {
}
bool DirAccessJAndroid::current_is_dir() const {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
return env->CallBooleanMethod(io, _dir_is_dir, id);
}
@@ -86,7 +86,7 @@ void DirAccessJAndroid::list_dir_end() {
if (id == 0)
return;
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
env->CallVoidMethod(io, _dir_close, id);
id = 0;
}
@@ -100,7 +100,7 @@ String DirAccessJAndroid::get_drive(int p_drive) {
}
Error DirAccessJAndroid::change_dir(String p_dir) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
if (p_dir == "" || p_dir == "." || (p_dir == ".." && current_dir == ""))
return OK;
@@ -146,7 +146,7 @@ bool DirAccessJAndroid::file_exists(String p_file) {
else
sd = current_dir.plus_file(p_file);
- FileAccessJAndroid *f = memnew(FileAccessJAndroid);
+ FileAccessAndroid *f = memnew(FileAccessAndroid);
bool exists = f->file_exists(sd);
memdelete(f);
@@ -154,14 +154,14 @@ bool DirAccessJAndroid::file_exists(String p_file) {
}
bool DirAccessJAndroid::dir_exists(String p_dir) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
String sd;
if (current_dir == "")
sd = p_dir;
else {
- if (p_dir.is_rel_path())
+ if (p_dir.is_relative_path())
sd = current_dir.plus_file(p_dir);
else
sd = fix_path(p_dir);
@@ -201,13 +201,12 @@ String DirAccessJAndroid::get_filesystem_type() const {
return "APK";
}
-//FileType get_file_type() const;
-size_t DirAccessJAndroid::get_space_left() {
+uint64_t DirAccessJAndroid::get_space_left() {
return 0;
}
void DirAccessJAndroid::setup(jobject p_io) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
io = p_io;
jclass c = env->GetObjectClass(io);
diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h
index 7d0def137a..cdf98187ed 100644
--- a/platform/android/dir_access_jandroid.h
+++ b/platform/android/dir_access_jandroid.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,7 +31,7 @@
#ifndef DIR_ACCESS_JANDROID_H
#define DIR_ACCESS_JANDROID_H
-#include "core/os/dir_access.h"
+#include "core/io/dir_access.h"
#include "java_godot_lib_jni.h"
#include <stdio.h>
@@ -74,10 +74,13 @@ public:
virtual Error rename(String p_from, String p_to);
virtual Error remove(String p_name);
+ virtual bool is_link(String p_file) { return false; }
+ virtual String read_link(String p_file) { return p_file; }
+ virtual Error create_link(String p_source, String p_target) { return FAILED; }
+
virtual String get_filesystem_type() const;
- //virtual FileType get_file_type() const;
- size_t get_space_left();
+ uint64_t get_space_left();
static void setup(jobject p_io);
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 3aa2fb5451..34f0064a5e 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,8 +30,7 @@
#include "display_server_android.h"
-#include "android_keys_utils.h"
-#include "core/project_settings.h"
+#include "core/config/project_settings.h"
#include "java_godot_io_wrapper.h"
#include "java_godot_wrapper.h"
#include "os_android.h"
@@ -39,7 +38,7 @@
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_device_vulkan.h"
#include "platform/android/vulkan/vulkan_context_android.h"
-#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
+#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
#endif
DisplayServerAndroid *DisplayServerAndroid::get_singleton() {
@@ -49,17 +48,16 @@ DisplayServerAndroid *DisplayServerAndroid::get_singleton() {
bool DisplayServerAndroid::has_feature(Feature p_feature) const {
switch (p_feature) {
//case FEATURE_CONSOLE_WINDOW:
- //case FEATURE_CURSOR_SHAPE:
+ case FEATURE_CURSOR_SHAPE:
//case FEATURE_CUSTOM_CURSOR_SHAPE:
//case FEATURE_GLOBAL_MENU:
//case FEATURE_HIDPI:
//case FEATURE_ICON:
//case FEATURE_IME:
- //case FEATURE_MOUSE:
+ case FEATURE_MOUSE:
//case FEATURE_MOUSE_WARP:
//case FEATURE_NATIVE_DIALOG:
//case FEATURE_NATIVE_ICON:
- //case FEATURE_NATIVE_VIDEO:
//case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_CLIPBOARD:
case FEATURE_KEEP_SCREEN_ON:
@@ -137,8 +135,11 @@ Size2i DisplayServerAndroid::screen_get_size(int p_screen) const {
}
Rect2i DisplayServerAndroid::screen_get_usable_rect(int p_screen) const {
- Size2i display_size = OS_Android::get_singleton()->get_display_size();
- return Rect2i(0, 0, display_size.width, display_size.height);
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ ERR_FAIL_COND_V(!godot_io_java, Rect2i());
+ int xywh[4];
+ godot_io_java->screen_get_usable_rect(xywh);
+ return Rect2i(xywh[0], xywh[1], xywh[2], xywh[3]);
}
int DisplayServerAndroid::screen_get_dpi(int p_screen) const {
@@ -194,24 +195,28 @@ void DisplayServerAndroid::window_set_input_text_callback(const Callable &p_call
}
void DisplayServerAndroid::window_set_rect_changed_callback(const Callable &p_callable, DisplayServer::WindowID p_window) {
- // Not supported on Android.
+ rect_changed_callback = p_callable;
}
void DisplayServerAndroid::window_set_drop_files_callback(const Callable &p_callable, DisplayServer::WindowID p_window) {
// Not supported on Android.
}
-void DisplayServerAndroid::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
+void DisplayServerAndroid::_window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred) const {
if (!p_callable.is_null()) {
const Variant *argp = &p_arg;
Variant ret;
Callable::CallError ce;
- p_callable.call((const Variant **)&argp, 1, ret, ce);
+ if (p_deferred) {
+ p_callable.call((const Variant **)&argp, 1, ret, ce);
+ } else {
+ p_callable.call_deferred((const Variant **)&argp, 1);
+ }
}
}
-void DisplayServerAndroid::send_window_event(DisplayServer::WindowEvent p_event) const {
- _window_callback(window_event_callback, int(p_event));
+void DisplayServerAndroid::send_window_event(DisplayServer::WindowEvent p_event, bool p_deferred) const {
+ _window_callback(window_event_callback, int(p_event), p_deferred);
}
void DisplayServerAndroid::send_input_event(const Ref<InputEvent> &p_event) const {
@@ -332,22 +337,15 @@ bool DisplayServerAndroid::can_any_window_draw() const {
return true;
}
-void DisplayServerAndroid::alert(const String &p_alert, const String &p_title) {
- GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
- ERR_FAIL_COND(!godot_java);
-
- godot_java->alert(p_alert, p_title);
-}
-
void DisplayServerAndroid::process_events() {
- // Nothing to do
+ Input::get_singleton()->flush_buffered_events();
}
Vector<String> DisplayServerAndroid::get_rendering_drivers_func() {
Vector<String> drivers;
-#ifdef OPENGL_ENABLED
- drivers.push_back("opengl");
+#ifdef GLES3_ENABLED
+ drivers.push_back("opengl3");
#endif
#ifdef VULKAN_ENABLED
drivers.push_back("vulkan");
@@ -356,10 +354,10 @@ Vector<String> DisplayServerAndroid::get_rendering_drivers_func() {
return drivers;
}
-DisplayServer *DisplayServerAndroid::create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
- DisplayServer *ds = memnew(DisplayServerAndroid(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
+DisplayServer *DisplayServerAndroid::create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ DisplayServer *ds = memnew(DisplayServerAndroid(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error));
if (r_error != OK) {
- ds->alert("Your video card driver does not support any of the supported Vulkan versions.", "Unable to initialize Video driver");
+ OS::get_singleton()->alert("Your video card driver does not support any of the supported Vulkan versions.", "Unable to initialize Video driver");
}
return ds;
}
@@ -375,10 +373,11 @@ void DisplayServerAndroid::reset_window() {
ERR_FAIL_COND(!native_window);
ERR_FAIL_COND(!context_vulkan);
+ VSyncMode last_vsync_mode = context_vulkan->get_vsync_mode(MAIN_WINDOW_ID);
context_vulkan->window_destroy(MAIN_WINDOW_ID);
Size2i display_size = OS_Android::get_singleton()->get_display_size();
- if (context_vulkan->window_create(native_window, display_size.width, display_size.height) == -1) {
+ if (context_vulkan->window_create(native_window, last_vsync_mode, display_size.width, display_size.height) == -1) {
memdelete(context_vulkan);
context_vulkan = nullptr;
ERR_FAIL_MSG("Failed to reset Vulkan window.");
@@ -387,7 +386,20 @@ void DisplayServerAndroid::reset_window() {
#endif
}
-DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+void DisplayServerAndroid::notify_surface_changed(int p_width, int p_height) {
+ if (rect_changed_callback.is_null()) {
+ return;
+ }
+
+ const Variant size = Rect2i(0, 0, p_width, p_height);
+ const Variant *sizep = &size;
+ Variant ret;
+ Callable::CallError ce;
+
+ rect_changed_callback.call(reinterpret_cast<const Variant **>(&sizep), 1, ret, ce);
+}
+
+DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
rendering_driver = p_rendering_driver;
// TODO: rendering_driver is broken, change when different drivers are supported again
@@ -395,13 +407,13 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis
keep_screen_on = GLOBAL_GET("display/window/energy_saving/keep_screen_on");
-#if defined(OPENGL_ENABLED)
- if (rendering_driver == "opengl") {
+#if defined(GLES3_ENABLED)
+ if (rendering_driver == "opengl3") {
bool gl_initialization_error = false;
- if (RasterizerGLES2::is_viable() == OK) {
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
+ if (RasterizerGLES3::is_viable() == OK) {
+ RasterizerGLES3::register_config();
+ RasterizerGLES3::make_current();
} else {
gl_initialization_error = true;
}
@@ -431,7 +443,7 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis
}
Size2i display_size = OS_Android::get_singleton()->get_display_size();
- if (context_vulkan->window_create(native_window, display_size.width, display_size.height) == -1) {
+ if (context_vulkan->window_create(native_window, p_vsync_mode, display_size.width, display_size.height) == -1) {
memdelete(context_vulkan);
context_vulkan = nullptr;
ERR_FAIL_MSG("Failed to create Vulkan window.");
@@ -440,11 +452,12 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis
rendering_device_vulkan = memnew(RenderingDeviceVulkan);
rendering_device_vulkan->initialize(context_vulkan);
- RasterizerRD::make_current();
+ RendererCompositorRD::make_current();
}
#endif
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
+ Input::get_singleton()->set_use_input_buffering(true); // Needed because events will come directly from the UI thread
r_error = OK;
}
@@ -464,234 +477,80 @@ DisplayServerAndroid::~DisplayServerAndroid() {
#endif
}
-void DisplayServerAndroid::process_joy_event(DisplayServerAndroid::JoypadEvent p_event) {
- switch (p_event.type) {
- case JOY_EVENT_BUTTON:
- Input::get_singleton()->joy_button(p_event.device, p_event.index, p_event.pressed);
- break;
- case JOY_EVENT_AXIS:
- Input::JoyAxis value;
- value.min = -1;
- value.value = p_event.value;
- Input::get_singleton()->joy_axis(p_event.device, p_event.index, value);
- break;
- case JOY_EVENT_HAT:
- Input::get_singleton()->joy_hat(p_event.device, p_event.hat);
- break;
- default:
- return;
- }
+void DisplayServerAndroid::process_accelerometer(const Vector3 &p_accelerometer) {
+ Input::get_singleton()->set_accelerometer(p_accelerometer);
}
-void DisplayServerAndroid::_set_key_modifier_state(Ref<InputEventWithModifiers> ev) {
- ev->set_shift(shift_mem);
- ev->set_alt(alt_mem);
- ev->set_metakey(meta_mem);
- ev->set_control(control_mem);
+void DisplayServerAndroid::process_gravity(const Vector3 &p_gravity) {
+ Input::get_singleton()->set_gravity(p_gravity);
}
-void DisplayServerAndroid::process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed) {
- Ref<InputEventKey> ev;
- ev.instance();
- int val = p_unicode_char;
- int keycode = android_get_keysym(p_keycode);
- int phy_keycode = android_get_keysym(p_scancode);
+void DisplayServerAndroid::process_magnetometer(const Vector3 &p_magnetometer) {
+ Input::get_singleton()->set_magnetometer(p_magnetometer);
+}
- if (keycode == KEY_SHIFT) {
- shift_mem = p_pressed;
- }
- if (keycode == KEY_ALT) {
- alt_mem = p_pressed;
- }
- if (keycode == KEY_CONTROL) {
- control_mem = p_pressed;
- }
- if (keycode == KEY_META) {
- meta_mem = p_pressed;
+void DisplayServerAndroid::process_gyroscope(const Vector3 &p_gyroscope) {
+ Input::get_singleton()->set_gyroscope(p_gyroscope);
+}
+
+void DisplayServerAndroid::mouse_set_mode(MouseMode p_mode) {
+ if (mouse_mode == p_mode) {
+ return;
}
- ev->set_keycode(keycode);
- ev->set_physical_keycode(phy_keycode);
- ev->set_unicode(val);
- ev->set_pressed(p_pressed);
-
- _set_key_modifier_state(ev);
-
- if (val == '\n') {
- ev->set_keycode(KEY_ENTER);
- } else if (val == 61448) {
- ev->set_keycode(KEY_BACKSPACE);
- ev->set_unicode(KEY_BACKSPACE);
- } else if (val == 61453) {
- ev->set_keycode(KEY_ENTER);
- ev->set_unicode(KEY_ENTER);
- } else if (p_keycode == 4) {
- OS_Android::get_singleton()->main_loop_request_go_back();
+ if (p_mode == MouseMode::MOUSE_MODE_HIDDEN) {
+ OS_Android::get_singleton()->get_godot_java()->get_godot_view()->set_pointer_icon(CURSOR_TYPE_NULL);
+ } else {
+ cursor_set_shape(cursor_shape);
}
- Input::get_singleton()->parse_input_event(ev);
-}
-
-void DisplayServerAndroid::process_touch(int p_what, int p_pointer, const Vector<DisplayServerAndroid::TouchPos> &p_points) {
- switch (p_what) {
- case 0: { //gesture begin
- if (touch.size()) {
- //end all if exist
- for (int i = 0; i < touch.size(); i++) {
- Ref<InputEventScreenTouch> ev;
- ev.instance();
- ev->set_index(touch[i].id);
- ev->set_pressed(false);
- ev->set_position(touch[i].pos);
- Input::get_singleton()->parse_input_event(ev);
- }
- }
-
- touch.resize(p_points.size());
- for (int i = 0; i < p_points.size(); i++) {
- touch.write[i].id = p_points[i].id;
- touch.write[i].pos = p_points[i].pos;
- }
-
- //send touch
- for (int i = 0; i < touch.size(); i++) {
- Ref<InputEventScreenTouch> ev;
- ev.instance();
- ev->set_index(touch[i].id);
- ev->set_pressed(true);
- ev->set_position(touch[i].pos);
- Input::get_singleton()->parse_input_event(ev);
- }
-
- } break;
- case 1: { //motion
- ERR_FAIL_COND(touch.size() != p_points.size());
-
- for (int i = 0; i < touch.size(); i++) {
- int idx = -1;
- for (int j = 0; j < p_points.size(); j++) {
- if (touch[i].id == p_points[j].id) {
- idx = j;
- break;
- }
- }
-
- ERR_CONTINUE(idx == -1);
-
- if (touch[i].pos == p_points[idx].pos)
- continue; //no move unncesearily
-
- Ref<InputEventScreenDrag> ev;
- ev.instance();
- ev->set_index(touch[i].id);
- ev->set_position(p_points[idx].pos);
- ev->set_relative(p_points[idx].pos - touch[i].pos);
- Input::get_singleton()->parse_input_event(ev);
- touch.write[i].pos = p_points[idx].pos;
- }
-
- } break;
- case 2: { //release
- if (touch.size()) {
- //end all if exist
- for (int i = 0; i < touch.size(); i++) {
- Ref<InputEventScreenTouch> ev;
- ev.instance();
- ev->set_index(touch[i].id);
- ev->set_pressed(false);
- ev->set_position(touch[i].pos);
- Input::get_singleton()->parse_input_event(ev);
- }
- touch.clear();
- }
- } break;
- case 3: { // add touch
- for (int i = 0; i < p_points.size(); i++) {
- if (p_points[i].id == p_pointer) {
- TouchPos tp = p_points[i];
- touch.push_back(tp);
-
- Ref<InputEventScreenTouch> ev;
- ev.instance();
-
- ev->set_index(tp.id);
- ev->set_pressed(true);
- ev->set_position(tp.pos);
- Input::get_singleton()->parse_input_event(ev);
-
- break;
- }
- }
- } break;
- case 4: { // remove touch
- for (int i = 0; i < touch.size(); i++) {
- if (touch[i].id == p_pointer) {
- Ref<InputEventScreenTouch> ev;
- ev.instance();
- ev->set_index(touch[i].id);
- ev->set_pressed(false);
- ev->set_position(touch[i].pos);
- Input::get_singleton()->parse_input_event(ev);
- touch.remove(i);
-
- break;
- }
- }
- } break;
+ if (p_mode == MouseMode::MOUSE_MODE_CAPTURED) {
+ OS_Android::get_singleton()->get_godot_java()->get_godot_view()->request_pointer_capture();
+ } else {
+ OS_Android::get_singleton()->get_godot_java()->get_godot_view()->release_pointer_capture();
}
+
+ mouse_mode = p_mode;
}
-void DisplayServerAndroid::process_hover(int p_type, Point2 p_pos) {
- // https://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER
- switch (p_type) {
- case 7: // hover move
- case 9: // hover enter
- case 10: { // hover exit
- Ref<InputEventMouseMotion> ev;
- ev.instance();
- _set_key_modifier_state(ev);
- ev->set_position(p_pos);
- ev->set_global_position(p_pos);
- ev->set_relative(p_pos - hover_prev_pos);
- Input::get_singleton()->parse_input_event(ev);
- hover_prev_pos = p_pos;
- } break;
- }
+DisplayServer::MouseMode DisplayServerAndroid::mouse_get_mode() const {
+ return mouse_mode;
}
-void DisplayServerAndroid::process_double_tap(Point2 p_pos) {
- Ref<InputEventMouseButton> ev;
- ev.instance();
- _set_key_modifier_state(ev);
- ev->set_position(p_pos);
- ev->set_global_position(p_pos);
- ev->set_pressed(false);
- ev->set_doubleclick(true);
- Input::get_singleton()->parse_input_event(ev);
+Point2i DisplayServerAndroid::mouse_get_position() const {
+ return Input::get_singleton()->get_mouse_position();
}
-void DisplayServerAndroid::process_scroll(Point2 p_pos) {
- Ref<InputEventPanGesture> ev;
- ev.instance();
- _set_key_modifier_state(ev);
- ev->set_position(p_pos);
- ev->set_delta(p_pos - scroll_prev_pos);
- Input::get_singleton()->parse_input_event(ev);
- scroll_prev_pos = p_pos;
+MouseButton DisplayServerAndroid::mouse_get_button_state() const {
+ return (MouseButton)Input::get_singleton()->get_mouse_button_mask();
}
-void DisplayServerAndroid::process_accelerometer(const Vector3 &p_accelerometer) {
- Input::get_singleton()->set_accelerometer(p_accelerometer);
+void DisplayServerAndroid::cursor_set_shape(DisplayServer::CursorShape p_shape) {
+ if (cursor_shape == p_shape) {
+ return;
+ }
+
+ cursor_shape = p_shape;
+
+ if (mouse_mode == MouseMode::MOUSE_MODE_VISIBLE || mouse_mode == MouseMode::MOUSE_MODE_CONFINED) {
+ OS_Android::get_singleton()->get_godot_java()->get_godot_view()->set_pointer_icon(android_cursors[cursor_shape]);
+ }
}
-void DisplayServerAndroid::process_gravity(const Vector3 &p_gravity) {
- Input::get_singleton()->set_gravity(p_gravity);
+DisplayServer::CursorShape DisplayServerAndroid::cursor_get_shape() const {
+ return cursor_shape;
}
-void DisplayServerAndroid::process_magnetometer(const Vector3 &p_magnetometer) {
- Input::get_singleton()->set_magnetometer(p_magnetometer);
+void DisplayServerAndroid::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
+#if defined(VULKAN_ENABLED)
+ context_vulkan->set_vsync_mode(p_window, p_vsync_mode);
+#endif
}
-void DisplayServerAndroid::process_gyroscope(const Vector3 &p_gyroscope) {
- Input::get_singleton()->set_gyroscope(p_gyroscope);
+DisplayServer::VSyncMode DisplayServerAndroid::window_get_vsync_mode(WindowID p_window) const {
+#if defined(VULKAN_ENABLED)
+ return context_vulkan->get_vsync_mode(p_window);
+#else
+ return DisplayServer::VSYNC_ENABLED;
+#endif
}
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index 5cdc69ee83..669a1c80e4 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -39,40 +39,35 @@ class RenderingDeviceVulkan;
#endif
class DisplayServerAndroid : public DisplayServer {
-public:
- struct TouchPos {
- int id;
- Point2 pos;
- };
-
- enum {
- JOY_EVENT_BUTTON = 0,
- JOY_EVENT_AXIS = 1,
- JOY_EVENT_HAT = 2
- };
-
- struct JoypadEvent {
- int device;
- int type;
- int index;
- bool pressed;
- float value;
- int hat;
- };
-
-private:
String rendering_driver;
- bool alt_mem = false;
- bool shift_mem = false;
- bool control_mem = false;
- bool meta_mem = false;
+ // https://developer.android.com/reference/android/view/PointerIcon
+ // mapping between Godot's cursor shape to Android's'
+ int android_cursors[CURSOR_MAX] = {
+ 1000, //CURSOR_ARROW
+ 1008, //CURSOR_IBEAM
+ 1002, //CURSOR_POINTIN
+ 1007, //CURSOR_CROSS
+ 1004, //CURSOR_WAIT
+ 1004, //CURSOR_BUSY
+ 1021, //CURSOR_DRAG
+ 1021, //CURSOR_CAN_DRO
+ 1000, //CURSOR_FORBIDD (no corresponding icon in Android's icon so fallback to default)
+ 1015, //CURSOR_VSIZE
+ 1014, //CURSOR_HSIZE
+ 1017, //CURSOR_BDIAGSI
+ 1016, //CURSOR_FDIAGSI
+ 1020, //CURSOR_MOVE
+ 1015, //CURSOR_VSPLIT
+ 1014, //CURSOR_HSPLIT
+ 1003, //CURSOR_HELP
+ };
+ const int CURSOR_TYPE_NULL = 0;
+ MouseMode mouse_mode = MouseMode::MOUSE_MODE_VISIBLE;
bool keep_screen_on;
- Vector<TouchPos> touch;
- Point2 hover_prev_pos; // needed to calculate the relative position on hover events
- Point2 scroll_prev_pos; // needed to calculate the relative position on scroll events
+ CursorShape cursor_shape = CursorShape::CURSOR_ARROW;
#if defined(VULKAN_ENABLED)
VulkanContextAndroid *context_vulkan;
@@ -84,98 +79,114 @@ private:
Callable window_event_callback;
Callable input_event_callback;
Callable input_text_callback;
+ Callable rect_changed_callback;
- void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
+ void _window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred = false) const;
static void _dispatch_input_events(const Ref<InputEvent> &p_event);
- void _set_key_modifier_state(Ref<InputEventWithModifiers> ev);
-
public:
static DisplayServerAndroid *get_singleton();
- virtual bool has_feature(Feature p_feature) const;
- virtual String get_name() const;
+ virtual bool has_feature(Feature p_feature) const override;
+ virtual String get_name() const override;
- virtual void clipboard_set(const String &p_text);
- virtual String clipboard_get() const;
+ virtual void clipboard_set(const String &p_text) override;
+ virtual String clipboard_get() const override;
- virtual void screen_set_keep_on(bool p_enable);
- virtual bool screen_is_kept_on() const;
+ virtual void screen_set_keep_on(bool p_enable) override;
+ virtual bool screen_is_kept_on() const override;
- virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW);
- virtual ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
+ virtual ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
- virtual int get_screen_count() const;
- virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
- virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
- virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
- virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
- virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
+ virtual int get_screen_count() const override;
+ virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
- virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
- virtual void virtual_keyboard_hide();
- virtual int virtual_keyboard_get_height() const;
+ virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1) override;
+ virtual void virtual_keyboard_hide() override;
+ virtual int virtual_keyboard_get_height() const override;
- virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
- virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
- virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
- virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
- virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
+ virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
- void send_window_event(WindowEvent p_event) const;
+ void send_window_event(WindowEvent p_event, bool p_deferred = false) const;
void send_input_event(const Ref<InputEvent> &p_event) const;
void send_input_text(const String &p_text) const;
- virtual Vector<WindowID> get_window_list() const;
- virtual WindowID get_window_at_screen_position(const Point2i &p_position) const;
- virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID);
- virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const;
- virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID);
- virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const;
- virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID);
- virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const;
- virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID);
- virtual void window_set_transient(WindowID p_window, WindowID p_parent);
- virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
- virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const;
- virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
- virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const;
- virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
- virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const;
- virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const;
- virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID);
- virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const;
- virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const;
- virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID);
- virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const;
- virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID);
- virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID);
- virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const;
- virtual bool can_any_window_draw() const;
-
- virtual void alert(const String &p_alert, const String &p_title);
-
- virtual void process_events();
+ virtual Vector<WindowID> get_window_list() const override;
+ virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
+ virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
+
+ virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool can_any_window_draw() const override;
+
+ virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
+
+ virtual void process_events() override;
void process_accelerometer(const Vector3 &p_accelerometer);
void process_gravity(const Vector3 &p_gravity);
void process_magnetometer(const Vector3 &p_magnetometer);
void process_gyroscope(const Vector3 &p_gyroscope);
- void process_touch(int p_what, int p_pointer, const Vector<TouchPos> &p_points);
- void process_hover(int p_type, Point2 p_pos);
- void process_double_tap(Point2 p_pos);
- void process_scroll(Point2 p_pos);
- void process_joy_event(JoypadEvent p_event);
- void process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed);
-
- static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+
+ virtual void cursor_set_shape(CursorShape p_shape) override;
+ virtual CursorShape cursor_get_shape() const override;
+
+ virtual void mouse_set_mode(MouseMode p_mode) override;
+ virtual MouseMode mouse_get_mode() const override;
+
+ static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
static Vector<String> get_rendering_drivers_func();
static void register_android_driver();
void reset_window();
+ void notify_surface_changed(int p_width, int p_height);
+
+ virtual Point2i mouse_get_position() const override;
+ virtual MouseButton mouse_get_button_state() const override;
- DisplayServerAndroid(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ DisplayServerAndroid(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
~DisplayServerAndroid();
};
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index 5e6cc3e4e2..8df61831c2 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -29,2743 +29,8 @@
/*************************************************************************/
#include "export.h"
-#include "gradle_export_util.h"
-#include "core/io/image_loader.h"
-#include "core/io/marshalls.h"
-#include "core/io/zip_io.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-#include "core/os/os.h"
-#include "core/project_settings.h"
-#include "core/version.h"
-#include "drivers/png/png_driver_common.h"
-#include "editor/editor_export.h"
-#include "editor/editor_log.h"
-#include "editor/editor_node.h"
-#include "editor/editor_settings.h"
-#include "main/splash.gen.h"
-#include "platform/android/export/gradle_export_util.h"
-#include "platform/android/logo.gen.h"
-#include "platform/android/plugin/godot_plugin_config.h"
-#include "platform/android/run_icon.gen.h"
-
-#include <string.h>
-
-static const char *android_perms[] = {
- "ACCESS_CHECKIN_PROPERTIES",
- "ACCESS_COARSE_LOCATION",
- "ACCESS_FINE_LOCATION",
- "ACCESS_LOCATION_EXTRA_COMMANDS",
- "ACCESS_MOCK_LOCATION",
- "ACCESS_NETWORK_STATE",
- "ACCESS_SURFACE_FLINGER",
- "ACCESS_WIFI_STATE",
- "ACCOUNT_MANAGER",
- "ADD_VOICEMAIL",
- "AUTHENTICATE_ACCOUNTS",
- "BATTERY_STATS",
- "BIND_ACCESSIBILITY_SERVICE",
- "BIND_APPWIDGET",
- "BIND_DEVICE_ADMIN",
- "BIND_INPUT_METHOD",
- "BIND_NFC_SERVICE",
- "BIND_NOTIFICATION_LISTENER_SERVICE",
- "BIND_PRINT_SERVICE",
- "BIND_REMOTEVIEWS",
- "BIND_TEXT_SERVICE",
- "BIND_VPN_SERVICE",
- "BIND_WALLPAPER",
- "BLUETOOTH",
- "BLUETOOTH_ADMIN",
- "BLUETOOTH_PRIVILEGED",
- "BRICK",
- "BROADCAST_PACKAGE_REMOVED",
- "BROADCAST_SMS",
- "BROADCAST_STICKY",
- "BROADCAST_WAP_PUSH",
- "CALL_PHONE",
- "CALL_PRIVILEGED",
- "CAMERA",
- "CAPTURE_AUDIO_OUTPUT",
- "CAPTURE_SECURE_VIDEO_OUTPUT",
- "CAPTURE_VIDEO_OUTPUT",
- "CHANGE_COMPONENT_ENABLED_STATE",
- "CHANGE_CONFIGURATION",
- "CHANGE_NETWORK_STATE",
- "CHANGE_WIFI_MULTICAST_STATE",
- "CHANGE_WIFI_STATE",
- "CLEAR_APP_CACHE",
- "CLEAR_APP_USER_DATA",
- "CONTROL_LOCATION_UPDATES",
- "DELETE_CACHE_FILES",
- "DELETE_PACKAGES",
- "DEVICE_POWER",
- "DIAGNOSTIC",
- "DISABLE_KEYGUARD",
- "DUMP",
- "EXPAND_STATUS_BAR",
- "FACTORY_TEST",
- "FLASHLIGHT",
- "FORCE_BACK",
- "GET_ACCOUNTS",
- "GET_PACKAGE_SIZE",
- "GET_TASKS",
- "GET_TOP_ACTIVITY_INFO",
- "GLOBAL_SEARCH",
- "HARDWARE_TEST",
- "INJECT_EVENTS",
- "INSTALL_LOCATION_PROVIDER",
- "INSTALL_PACKAGES",
- "INSTALL_SHORTCUT",
- "INTERNAL_SYSTEM_WINDOW",
- "INTERNET",
- "KILL_BACKGROUND_PROCESSES",
- "LOCATION_HARDWARE",
- "MANAGE_ACCOUNTS",
- "MANAGE_APP_TOKENS",
- "MANAGE_DOCUMENTS",
- "MASTER_CLEAR",
- "MEDIA_CONTENT_CONTROL",
- "MODIFY_AUDIO_SETTINGS",
- "MODIFY_PHONE_STATE",
- "MOUNT_FORMAT_FILESYSTEMS",
- "MOUNT_UNMOUNT_FILESYSTEMS",
- "NFC",
- "PERSISTENT_ACTIVITY",
- "PROCESS_OUTGOING_CALLS",
- "READ_CALENDAR",
- "READ_CALL_LOG",
- "READ_CONTACTS",
- "READ_EXTERNAL_STORAGE",
- "READ_FRAME_BUFFER",
- "READ_HISTORY_BOOKMARKS",
- "READ_INPUT_STATE",
- "READ_LOGS",
- "READ_PHONE_STATE",
- "READ_PROFILE",
- "READ_SMS",
- "READ_SOCIAL_STREAM",
- "READ_SYNC_SETTINGS",
- "READ_SYNC_STATS",
- "READ_USER_DICTIONARY",
- "REBOOT",
- "RECEIVE_BOOT_COMPLETED",
- "RECEIVE_MMS",
- "RECEIVE_SMS",
- "RECEIVE_WAP_PUSH",
- "RECORD_AUDIO",
- "REORDER_TASKS",
- "RESTART_PACKAGES",
- "SEND_RESPOND_VIA_MESSAGE",
- "SEND_SMS",
- "SET_ACTIVITY_WATCHER",
- "SET_ALARM",
- "SET_ALWAYS_FINISH",
- "SET_ANIMATION_SCALE",
- "SET_DEBUG_APP",
- "SET_ORIENTATION",
- "SET_POINTER_SPEED",
- "SET_PREFERRED_APPLICATIONS",
- "SET_PROCESS_LIMIT",
- "SET_TIME",
- "SET_TIME_ZONE",
- "SET_WALLPAPER",
- "SET_WALLPAPER_HINTS",
- "SIGNAL_PERSISTENT_PROCESSES",
- "STATUS_BAR",
- "SUBSCRIBED_FEEDS_READ",
- "SUBSCRIBED_FEEDS_WRITE",
- "SYSTEM_ALERT_WINDOW",
- "TRANSMIT_IR",
- "UNINSTALL_SHORTCUT",
- "UPDATE_DEVICE_STATS",
- "USE_CREDENTIALS",
- "USE_SIP",
- "VIBRATE",
- "WAKE_LOCK",
- "WRITE_APN_SETTINGS",
- "WRITE_CALENDAR",
- "WRITE_CALL_LOG",
- "WRITE_CONTACTS",
- "WRITE_EXTERNAL_STORAGE",
- "WRITE_GSERVICES",
- "WRITE_HISTORY_BOOKMARKS",
- "WRITE_PROFILE",
- "WRITE_SECURE_SETTINGS",
- "WRITE_SETTINGS",
- "WRITE_SMS",
- "WRITE_SOCIAL_STREAM",
- "WRITE_SYNC_SETTINGS",
- "WRITE_USER_DICTIONARY",
- nullptr
-};
-
-static const char *SPLASH_IMAGE_EXPORT_PATH = "res/drawable/splash.png";
-static const char *SPLASH_BG_COLOR_PATH = "res/drawable/splash_bg_color.png";
-
-struct LauncherIcon {
- const char *export_path;
- int dimensions;
-};
-
-static const int icon_densities_count = 6;
-static const char *launcher_icon_option = "launcher_icons/main_192x192";
-static const char *launcher_adaptive_icon_foreground_option = "launcher_icons/adaptive_foreground_432x432";
-static const char *launcher_adaptive_icon_background_option = "launcher_icons/adaptive_background_432x432";
-
-static const LauncherIcon launcher_icons[icon_densities_count] = {
- { "res/mipmap-xxxhdpi-v4/icon.png", 192 },
- { "res/mipmap-xxhdpi-v4/icon.png", 144 },
- { "res/mipmap-xhdpi-v4/icon.png", 96 },
- { "res/mipmap-hdpi-v4/icon.png", 72 },
- { "res/mipmap-mdpi-v4/icon.png", 48 },
- { "res/mipmap/icon.png", 192 }
-};
-
-static const LauncherIcon launcher_adaptive_icon_foregrounds[icon_densities_count] = {
- { "res/mipmap-xxxhdpi-v4/icon_foreground.png", 432 },
- { "res/mipmap-xxhdpi-v4/icon_foreground.png", 324 },
- { "res/mipmap-xhdpi-v4/icon_foreground.png", 216 },
- { "res/mipmap-hdpi-v4/icon_foreground.png", 162 },
- { "res/mipmap-mdpi-v4/icon_foreground.png", 108 },
- { "res/mipmap/icon_foreground.png", 432 }
-};
-
-static const LauncherIcon launcher_adaptive_icon_backgrounds[icon_densities_count] = {
- { "res/mipmap-xxxhdpi-v4/icon_background.png", 432 },
- { "res/mipmap-xxhdpi-v4/icon_background.png", 324 },
- { "res/mipmap-xhdpi-v4/icon_background.png", 216 },
- { "res/mipmap-hdpi-v4/icon_background.png", 162 },
- { "res/mipmap-mdpi-v4/icon_background.png", 108 },
- { "res/mipmap/icon_background.png", 432 }
-};
-
-class EditorExportPlatformAndroid : public EditorExportPlatform {
- GDCLASS(EditorExportPlatformAndroid, EditorExportPlatform);
-
- Ref<ImageTexture> logo;
- Ref<ImageTexture> run_icon;
-
- struct Device {
- String id;
- String name;
- String description;
- int api_level;
- };
-
- struct APKExportData {
- zipFile apk;
- EditorProgress *ep;
- };
-
- Vector<PluginConfig> plugins;
- String last_plugin_names;
- uint64_t last_custom_build_time = 0;
- volatile bool plugins_changed;
- Mutex plugins_lock;
- Vector<Device> devices;
- volatile bool devices_changed;
- Mutex device_lock;
- Thread *check_for_changes_thread;
- volatile bool quit_request;
-
- static void _check_for_changes_poll_thread(void *ud) {
- EditorExportPlatformAndroid *ea = (EditorExportPlatformAndroid *)ud;
-
- while (!ea->quit_request) {
- // Check for plugins updates
- {
- // Nothing to do if we already know the plugins have changed.
- if (!ea->plugins_changed) {
- Vector<PluginConfig> loaded_plugins = get_plugins();
-
- MutexLock lock(ea->plugins_lock);
-
- if (ea->plugins.size() != loaded_plugins.size()) {
- ea->plugins_changed = true;
- } else {
- for (int i = 0; i < ea->plugins.size(); i++) {
- if (ea->plugins[i].name != loaded_plugins[i].name) {
- ea->plugins_changed = true;
- break;
- }
- }
- }
-
- if (ea->plugins_changed) {
- ea->plugins = loaded_plugins;
- }
- }
- }
-
- // Check for devices updates
- String adb = EditorSettings::get_singleton()->get("export/android/adb");
- if (FileAccess::exists(adb)) {
- String devices;
- List<String> args;
- args.push_back("devices");
- int ec;
- OS::get_singleton()->execute(adb, args, true, nullptr, &devices, &ec);
-
- Vector<String> ds = devices.split("\n");
- Vector<String> ldevices;
- for (int i = 1; i < ds.size(); i++) {
- String d = ds[i];
- int dpos = d.find("device");
- if (dpos == -1) {
- continue;
- }
- d = d.substr(0, dpos).strip_edges();
- ldevices.push_back(d);
- }
-
- MutexLock lock(ea->device_lock);
-
- bool different = false;
-
- if (ea->devices.size() != ldevices.size()) {
- different = true;
- } else {
- for (int i = 0; i < ea->devices.size(); i++) {
- if (ea->devices[i].id != ldevices[i]) {
- different = true;
- break;
- }
- }
- }
-
- if (different) {
- Vector<Device> ndevices;
-
- for (int i = 0; i < ldevices.size(); i++) {
- Device d;
- d.id = ldevices[i];
- for (int j = 0; j < ea->devices.size(); j++) {
- if (ea->devices[j].id == ldevices[i]) {
- d.description = ea->devices[j].description;
- d.name = ea->devices[j].name;
- d.api_level = ea->devices[j].api_level;
- }
- }
-
- if (d.description == "") {
- //in the oven, request!
- args.clear();
- args.push_back("-s");
- args.push_back(d.id);
- args.push_back("shell");
- args.push_back("getprop");
- int ec2;
- String dp;
-
- OS::get_singleton()->execute(adb, args, true, nullptr, &dp, &ec2);
-
- Vector<String> props = dp.split("\n");
- String vendor;
- String device;
- d.description = "Device ID: " + d.id + "\n";
- d.api_level = 0;
- for (int j = 0; j < props.size(); j++) {
- // got information by `shell cat /system/build.prop` before and its format is "property=value"
- // it's now changed to use `shell getporp` because of permission issue with Android 8.0 and above
- // its format is "[property]: [value]" so changed it as like build.prop
- String p = props[j];
- p = p.replace("]: ", "=");
- p = p.replace("[", "");
- p = p.replace("]", "");
-
- if (p.begins_with("ro.product.model=")) {
- device = p.get_slice("=", 1).strip_edges();
- } else if (p.begins_with("ro.product.brand=")) {
- vendor = p.get_slice("=", 1).strip_edges().capitalize();
- } else if (p.begins_with("ro.build.display.id=")) {
- d.description += "Build: " + p.get_slice("=", 1).strip_edges() + "\n";
- } else if (p.begins_with("ro.build.version.release=")) {
- d.description += "Release: " + p.get_slice("=", 1).strip_edges() + "\n";
- } else if (p.begins_with("ro.build.version.sdk=")) {
- d.api_level = p.get_slice("=", 1).to_int();
- } else if (p.begins_with("ro.product.cpu.abi=")) {
- d.description += "CPU: " + p.get_slice("=", 1).strip_edges() + "\n";
- } else if (p.begins_with("ro.product.manufacturer=")) {
- d.description += "Manufacturer: " + p.get_slice("=", 1).strip_edges() + "\n";
- } else if (p.begins_with("ro.board.platform=")) {
- d.description += "Chipset: " + p.get_slice("=", 1).strip_edges() + "\n";
- } else if (p.begins_with("ro.opengles.version=")) {
- uint32_t opengl = p.get_slice("=", 1).to_int();
- d.description += "OpenGL: " + itos(opengl >> 16) + "." + itos((opengl >> 8) & 0xFF) + "." + itos((opengl)&0xFF) + "\n";
- }
- }
-
- d.name = vendor + " " + device;
- if (device == String()) {
- continue;
- }
- }
-
- ndevices.push_back(d);
- }
-
- ea->devices = ndevices;
- ea->devices_changed = true;
- }
- }
-
- uint64_t sleep = 200;
- uint64_t wait = 3000000;
- uint64_t time = OS::get_singleton()->get_ticks_usec();
- while (OS::get_singleton()->get_ticks_usec() - time < wait) {
- OS::get_singleton()->delay_usec(1000 * sleep);
- if (ea->quit_request) {
- break;
- }
- }
- }
-
- if (EditorSettings::get_singleton()->get("export/android/shutdown_adb_on_exit")) {
- String adb = EditorSettings::get_singleton()->get("export/android/adb");
- if (!FileAccess::exists(adb)) {
- return; //adb not configured
- }
-
- List<String> args;
- args.push_back("kill-server");
- OS::get_singleton()->execute(adb, args, true);
- };
- }
-
- String get_project_name(const String &p_name) const {
- String aname;
- if (p_name != "") {
- aname = p_name;
- } else {
- aname = ProjectSettings::get_singleton()->get("application/config/name");
- }
-
- if (aname == "") {
- aname = VERSION_NAME;
- }
-
- return aname;
- }
-
- String get_package_name(const String &p_package) const {
- String pname = p_package;
- String basename = ProjectSettings::get_singleton()->get("application/config/name");
- basename = basename.to_lower();
-
- String name;
- bool first = true;
- for (int i = 0; i < basename.length(); i++) {
- char32_t c = basename[i];
- if (c >= '0' && c <= '9' && first) {
- continue;
- }
- if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
- name += String::chr(c);
- first = false;
- }
- }
- if (name == "") {
- name = "noname";
- }
-
- pname = pname.replace("$genname", name);
-
- return pname;
- }
-
- bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
- String pname = p_package;
-
- if (pname.length() == 0) {
- if (r_error) {
- *r_error = TTR("Package name is missing.");
- }
- return false;
- }
-
- int segments = 0;
- bool first = true;
- for (int i = 0; i < pname.length(); i++) {
- char32_t c = pname[i];
- if (first && c == '.') {
- if (r_error) {
- *r_error = TTR("Package segments must be of non-zero length.");
- }
- return false;
- }
- if (c == '.') {
- segments++;
- first = true;
- continue;
- }
- if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) {
- if (r_error) {
- *r_error = vformat(TTR("The character '%s' is not allowed in Android application package names."), String::chr(c));
- }
- return false;
- }
- if (first && (c >= '0' && c <= '9')) {
- if (r_error) {
- *r_error = TTR("A digit cannot be the first character in a package segment.");
- }
- return false;
- }
- if (first && c == '_') {
- if (r_error) {
- *r_error = vformat(TTR("The character '%s' cannot be the first character in a package segment."), String::chr(c));
- }
- return false;
- }
- first = false;
- }
-
- if (segments == 0) {
- if (r_error) {
- *r_error = TTR("The package must have at least one '.' separator.");
- }
- return false;
- }
-
- if (first) {
- if (r_error) {
- *r_error = TTR("Package segments must be of non-zero length.");
- }
- return false;
- }
-
- return true;
- }
-
- static bool _should_compress_asset(const String &p_path, const Vector<uint8_t> &p_data) {
- /*
- * By not compressing files with little or not benefit in doing so,
- * a performance gain is expected attime. Moreover, if the APK is
- * zip-aligned, assets stored as they are can be efficiently read by
- * Android by memory-mapping them.
- */
-
- // -- Unconditional uncompress to mimic AAPT plus some other
-
- static const char *unconditional_compress_ext[] = {
- // From https://github.com/android/platform_frameworks_base/blob/master/tools/aapt/Package.cpp
- // These formats are already compressed, or don't compress well:
- ".jpg", ".jpeg", ".png", ".gif",
- ".wav", ".mp2", ".mp3", ".ogg", ".aac",
- ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
- ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
- ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
- ".amr", ".awb", ".wma", ".wmv",
- // Godot-specific:
- ".webp", // Same reasoning as .png
- ".cfb", // Don't let small config files slow-down startup
- ".scn", // Binary scenes are usually already compressed
- ".stex", // Streamable textures are usually already compressed
- // Trailer for easier processing
- nullptr
- };
-
- for (const char **ext = unconditional_compress_ext; *ext; ++ext) {
- if (p_path.to_lower().ends_with(String(*ext))) {
- return false;
- }
- }
-
- // -- Compressed resource?
-
- if (p_data.size() >= 4 && p_data[0] == 'R' && p_data[1] == 'S' && p_data[2] == 'C' && p_data[3] == 'C') {
- // Already compressed
- return false;
- }
-
- // --- TODO: Decide on texture resources according to their image compression setting
-
- return true;
- }
-
- static zip_fileinfo get_zip_fileinfo() {
- OS::Time time = OS::get_singleton()->get_time();
- OS::Date date = OS::get_singleton()->get_date();
-
- zip_fileinfo zipfi;
- zipfi.tmz_date.tm_hour = time.hour;
- zipfi.tmz_date.tm_mday = date.day;
- zipfi.tmz_date.tm_min = time.min;
- zipfi.tmz_date.tm_mon = date.month;
- zipfi.tmz_date.tm_sec = time.sec;
- zipfi.tmz_date.tm_year = date.year;
- zipfi.dosDate = 0;
- zipfi.external_fa = 0;
- zipfi.internal_fa = 0;
-
- return zipfi;
- }
-
- static Vector<String> get_abis() {
- Vector<String> abis;
- abis.push_back("armeabi-v7a");
- abis.push_back("arm64-v8a");
- abis.push_back("x86");
- abis.push_back("x86_64");
- return abis;
- }
-
- /// List the gdap files in the directory specified by the p_path parameter.
- static Vector<String> list_gdap_files(const String &p_path) {
- Vector<String> dir_files;
- DirAccessRef da = DirAccess::open(p_path);
- if (da) {
- da->list_dir_begin();
- while (true) {
- String file = da->get_next();
- if (file == "") {
- break;
- }
-
- if (da->current_is_dir() || da->current_is_hidden()) {
- continue;
- }
-
- if (file.ends_with(PLUGIN_CONFIG_EXT)) {
- dir_files.push_back(file);
- }
- }
- da->list_dir_end();
- }
-
- return dir_files;
- }
-
- static Vector<PluginConfig> get_plugins() {
- Vector<PluginConfig> loaded_plugins;
-
- String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins");
-
- // Add the prebuilt plugins
- loaded_plugins.append_array(get_prebuilt_plugins(plugins_dir));
-
- if (DirAccess::exists(plugins_dir)) {
- Vector<String> plugins_filenames = list_gdap_files(plugins_dir);
-
- if (!plugins_filenames.empty()) {
- Ref<ConfigFile> config_file = memnew(ConfigFile);
- for (int i = 0; i < plugins_filenames.size(); i++) {
- PluginConfig config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i]));
- if (config.valid_config) {
- loaded_plugins.push_back(config);
- } else {
- print_error("Invalid plugin config file " + plugins_filenames[i]);
- }
- }
- }
- }
-
- return loaded_plugins;
- }
-
- static Vector<PluginConfig> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) {
- Vector<PluginConfig> enabled_plugins;
- Vector<PluginConfig> all_plugins = get_plugins();
- for (int i = 0; i < all_plugins.size(); i++) {
- PluginConfig plugin = all_plugins[i];
- bool enabled = p_presets->get("plugins/" + plugin.name);
- if (enabled) {
- enabled_plugins.push_back(plugin);
- }
- }
-
- return enabled_plugins;
- }
-
- static Error store_in_apk(APKExportData *ed, const String &p_path, const Vector<uint8_t> &p_data, int compression_method = Z_DEFLATED) {
- zip_fileinfo zipfi = get_zip_fileinfo();
- zipOpenNewFileInZip(ed->apk,
- p_path.utf8().get_data(),
- &zipfi,
- nullptr,
- 0,
- nullptr,
- 0,
- nullptr,
- compression_method,
- Z_DEFAULT_COMPRESSION);
-
- zipWriteInFileInZip(ed->apk, p_data.ptr(), p_data.size());
- zipCloseFileInZip(ed->apk);
-
- return OK;
- }
-
- static Error save_apk_so(void *p_userdata, const SharedObject &p_so) {
- if (!p_so.path.get_file().begins_with("lib")) {
- String err = "Android .so file names must start with \"lib\", but got: " + p_so.path;
- ERR_PRINT(err);
- return FAILED;
- }
- APKExportData *ed = (APKExportData *)p_userdata;
- Vector<String> abis = get_abis();
- bool exported = false;
- for (int i = 0; i < p_so.tags.size(); ++i) {
- // shared objects can be fat (compatible with multiple ABIs)
- int abi_index = abis.find(p_so.tags[i]);
- if (abi_index != -1) {
- exported = true;
- String abi = abis[abi_index];
- String dst_path = String("lib").plus_file(abi).plus_file(p_so.path.get_file());
- Vector<uint8_t> array = FileAccess::get_file_as_array(p_so.path);
- Error store_err = store_in_apk(ed, dst_path, array);
- ERR_FAIL_COND_V_MSG(store_err, store_err, "Cannot store in apk file '" + dst_path + "'.");
- }
- }
- if (!exported) {
- String abis_string = String(" ").join(abis);
- String err = "Cannot determine ABI for library \"" + p_so.path + "\". One of the supported ABIs must be used as a tag: " + abis_string;
- ERR_PRINT(err);
- return FAILED;
- }
- return OK;
- }
-
- static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
- APKExportData *ed = (APKExportData *)p_userdata;
- String dst_path = p_path.replace_first("res://", "assets/");
-
- store_in_apk(ed, dst_path, p_data, _should_compress_asset(p_path, p_data) ? Z_DEFLATED : 0);
- return OK;
- }
-
- static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
- return OK;
- }
-
- void _get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) {
- const char **aperms = android_perms;
- while (*aperms) {
- bool enabled = p_preset->get("permissions/" + String(*aperms).to_lower());
- if (enabled) {
- r_permissions.push_back("android.permission." + String(*aperms));
- }
- aperms++;
- }
- PackedStringArray user_perms = p_preset->get("permissions/custom_permissions");
- for (int i = 0; i < user_perms.size(); i++) {
- String user_perm = user_perms[i].strip_edges();
- if (!user_perm.empty()) {
- r_permissions.push_back(user_perm);
- }
- }
- if (p_give_internet) {
- if (r_permissions.find("android.permission.INTERNET") == -1) {
- r_permissions.push_back("android.permission.INTERNET");
- }
- }
-
- int xr_mode_index = p_preset->get("xr_features/xr_mode");
- if (xr_mode_index == 1 /* XRMode.OVR */) {
- int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
- if (hand_tracking_index > 0) {
- if (r_permissions.find("com.oculus.permission.HAND_TRACKING") == -1) {
- r_permissions.push_back("com.oculus.permission.HAND_TRACKING");
- }
- }
- }
- }
-
- void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) {
- String manifest_text =
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
- " xmlns:tools=\"http://schemas.android.com/tools\">\n";
-
- manifest_text += _get_screen_sizes_tag(p_preset);
- manifest_text += _get_gles_tag();
-
- Vector<String> perms;
- _get_permissions(p_preset, p_give_internet, perms);
- for (int i = 0; i < perms.size(); i++) {
- manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", perms.get(i));
- }
-
- manifest_text += _get_xr_features_tag(p_preset);
- manifest_text += _get_instrumentation_tag(p_preset);
- String plugins_names = get_plugins_names(get_enabled_plugins(p_preset));
- manifest_text += _get_application_tag(p_preset, plugins_names);
- manifest_text += "</manifest>\n";
- String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"));
- store_string_at_path(manifest_path, manifest_text);
- }
-
- void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) {
- // Leaving the unused types commented because looking these constants up
- // again later would be annoying
- // const int CHUNK_AXML_FILE = 0x00080003;
- // const int CHUNK_RESOURCEIDS = 0x00080180;
- const int CHUNK_STRINGS = 0x001C0001;
- // const int CHUNK_XML_END_NAMESPACE = 0x00100101;
- const int CHUNK_XML_END_TAG = 0x00100103;
- // const int CHUNK_XML_START_NAMESPACE = 0x00100100;
- const int CHUNK_XML_START_TAG = 0x00100102;
- // const int CHUNK_XML_TEXT = 0x00100104;
- const int UTF8_FLAG = 0x00000100;
-
- Vector<String> string_table;
-
- uint32_t ofs = 8;
-
- uint32_t string_count = 0;
- //uint32_t styles_count = 0;
- uint32_t string_flags = 0;
- uint32_t string_data_offset = 0;
-
- //uint32_t styles_offset = 0;
- uint32_t string_table_begins = 0;
- uint32_t string_table_ends = 0;
- Vector<uint8_t> stable_extra;
-
- String version_name = p_preset->get("version/name");
- int version_code = p_preset->get("version/code");
- String package_name = p_preset->get("package/unique_name");
-
- int orientation = p_preset->get("screen/orientation");
-
- bool screen_support_small = p_preset->get("screen/support_small");
- bool screen_support_normal = p_preset->get("screen/support_normal");
- bool screen_support_large = p_preset->get("screen/support_large");
- bool screen_support_xlarge = p_preset->get("screen/support_xlarge");
-
- int xr_mode_index = p_preset->get("xr_features/xr_mode");
- bool focus_awareness = p_preset->get("xr_features/focus_awareness");
-
- String plugins_names = get_plugins_names(get_enabled_plugins(p_preset));
-
- Vector<String> perms;
- // Write permissions into the perms variable.
- _get_permissions(p_preset, p_give_internet, perms);
-
- while (ofs < (uint32_t)p_manifest.size()) {
- uint32_t chunk = decode_uint32(&p_manifest[ofs]);
- uint32_t size = decode_uint32(&p_manifest[ofs + 4]);
-
- switch (chunk) {
- case CHUNK_STRINGS: {
- int iofs = ofs + 8;
-
- string_count = decode_uint32(&p_manifest[iofs]);
- //styles_count = decode_uint32(&p_manifest[iofs + 4]);
- string_flags = decode_uint32(&p_manifest[iofs + 8]);
- string_data_offset = decode_uint32(&p_manifest[iofs + 12]);
- //styles_offset = decode_uint32(&p_manifest[iofs + 16]);
- /*
- printf("string count: %i\n",string_count);
- printf("flags: %i\n",string_flags);
- printf("sdata ofs: %i\n",string_data_offset);
- printf("styles ofs: %i\n",styles_offset);
- */
- uint32_t st_offset = iofs + 20;
- string_table.resize(string_count);
- uint32_t string_end = 0;
-
- string_table_begins = st_offset;
-
- for (uint32_t i = 0; i < string_count; i++) {
- uint32_t string_at = decode_uint32(&p_manifest[st_offset + i * 4]);
- string_at += st_offset + string_count * 4;
-
- ERR_FAIL_COND_MSG(string_flags & UTF8_FLAG, "Unimplemented, can't read UTF-8 string table.");
-
- if (string_flags & UTF8_FLAG) {
- } else {
- uint32_t len = decode_uint16(&p_manifest[string_at]);
- Vector<char32_t> ucstring;
- ucstring.resize(len + 1);
- for (uint32_t j = 0; j < len; j++) {
- uint16_t c = decode_uint16(&p_manifest[string_at + 2 + 2 * j]);
- ucstring.write[j] = c;
- }
- string_end = MAX(string_at + 2 + 2 * len, string_end);
- ucstring.write[len] = 0;
- string_table.write[i] = ucstring.ptr();
- }
- }
-
- for (uint32_t i = string_end; i < (ofs + size); i++) {
- stable_extra.push_back(p_manifest[i]);
- }
-
- string_table_ends = ofs + size;
-
- } break;
- case CHUNK_XML_START_TAG: {
- int iofs = ofs + 8;
- uint32_t name = decode_uint32(&p_manifest[iofs + 12]);
-
- String tname = string_table[name];
- uint32_t attrcount = decode_uint32(&p_manifest[iofs + 20]);
- iofs += 28;
- bool is_focus_aware_metadata = false;
-
- for (uint32_t i = 0; i < attrcount; i++) {
- uint32_t attr_nspace = decode_uint32(&p_manifest[iofs]);
- uint32_t attr_name = decode_uint32(&p_manifest[iofs + 4]);
- uint32_t attr_value = decode_uint32(&p_manifest[iofs + 8]);
- uint32_t attr_resid = decode_uint32(&p_manifest[iofs + 16]);
-
- const String value = (attr_value != 0xFFFFFFFF) ? string_table[attr_value] : "Res #" + itos(attr_resid);
- String attrname = string_table[attr_name];
- const String nspace = (attr_nspace != 0xFFFFFFFF) ? string_table[attr_nspace] : "";
-
- //replace project information
- if (tname == "manifest" && attrname == "package") {
- string_table.write[attr_value] = get_package_name(package_name);
- }
-
- if (tname == "manifest" && attrname == "versionCode") {
- encode_uint32(version_code, &p_manifest.write[iofs + 16]);
- }
-
- if (tname == "manifest" && attrname == "versionName") {
- if (attr_value == 0xFFFFFFFF) {
- WARN_PRINT("Version name in a resource, should be plain text");
- } else {
- string_table.write[attr_value] = version_name;
- }
- }
-
- if (tname == "instrumentation" && attrname == "targetPackage") {
- string_table.write[attr_value] = get_package_name(package_name);
- }
-
- if (tname == "activity" && attrname == "screenOrientation") {
- encode_uint32(orientation == 0 ? 0 : 1, &p_manifest.write[iofs + 16]);
- }
-
- if (tname == "supports-screens") {
- if (attrname == "smallScreens") {
- encode_uint32(screen_support_small ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
-
- } else if (attrname == "normalScreens") {
- encode_uint32(screen_support_normal ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
-
- } else if (attrname == "largeScreens") {
- encode_uint32(screen_support_large ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
-
- } else if (attrname == "xlargeScreens") {
- encode_uint32(screen_support_xlarge ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
- }
- }
-
- // FIXME: `attr_value != 0xFFFFFFFF` below added as a stopgap measure for GH-32553,
- // but the issue should be debugged further and properly addressed.
- if (tname == "meta-data" && attrname == "name" && value == "xr_mode_metadata_name") {
- // Update the meta-data 'android:name' attribute based on the selected XR mode.
- if (xr_mode_index == 1 /* XRMode.OVR */) {
- string_table.write[attr_value] = "com.samsung.android.vr.application.mode";
- }
- }
-
- if (tname == "meta-data" && attrname == "value" && value == "xr_mode_metadata_value") {
- // Update the meta-data 'android:value' attribute based on the selected XR mode.
- if (xr_mode_index == 1 /* XRMode.OVR */) {
- string_table.write[attr_value] = "vr_only";
- }
- }
-
- if (tname == "meta-data" && attrname == "value" && is_focus_aware_metadata) {
- // Update the focus awareness meta-data value
- encode_uint32(xr_mode_index == /* XRMode.OVR */ 1 && focus_awareness ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
- }
-
- if (tname == "meta-data" && attrname == "value" && value == "plugins_value" && !plugins_names.empty()) {
- // Update the meta-data 'android:value' attribute with the list of enabled plugins.
- string_table.write[attr_value] = plugins_names;
- }
-
- is_focus_aware_metadata = tname == "meta-data" && attrname == "name" && value == "com.oculus.vr.focusaware";
- iofs += 20;
- }
-
- } break;
- case CHUNK_XML_END_TAG: {
- int iofs = ofs + 8;
- uint32_t name = decode_uint32(&p_manifest[iofs + 12]);
- String tname = string_table[name];
-
- if (tname == "uses-feature") {
- Vector<String> feature_names;
- Vector<bool> feature_required_list;
- Vector<int> feature_versions;
-
- if (xr_mode_index == 1 /* XRMode.OVR */) {
- // Check for degrees of freedom
- int dof_index = p_preset->get("xr_features/degrees_of_freedom"); // 0: none, 1: 3dof and 6dof, 2: 6dof
-
- if (dof_index > 0) {
- feature_names.push_back("android.hardware.vr.headtracking");
- feature_required_list.push_back(dof_index == 2);
- feature_versions.push_back(1);
- }
-
- // Check for hand tracking
- int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
- if (hand_tracking_index > 0) {
- feature_names.push_back("oculus.software.handtracking");
- feature_required_list.push_back(hand_tracking_index == 2);
- feature_versions.push_back(-1); // no version attribute should be added.
- }
- }
-
- if (feature_names.size() > 0) {
- ofs += 24; // skip over end tag
-
- // save manifest ending so we can restore it
- Vector<uint8_t> manifest_end;
- uint32_t manifest_cur_size = p_manifest.size();
-
- manifest_end.resize(p_manifest.size() - ofs);
- memcpy(manifest_end.ptrw(), &p_manifest[ofs], manifest_end.size());
-
- int32_t attr_name_string = string_table.find("name");
- ERR_FAIL_COND_MSG(attr_name_string == -1, "Template does not have 'name' attribute.");
-
- int32_t ns_android_string = string_table.find("http://schemas.android.com/apk/res/android");
- if (ns_android_string == -1) {
- string_table.push_back("http://schemas.android.com/apk/res/android");
- ns_android_string = string_table.size() - 1;
- }
-
- int32_t attr_uses_feature_string = string_table.find("uses-feature");
- if (attr_uses_feature_string == -1) {
- string_table.push_back("uses-feature");
- attr_uses_feature_string = string_table.size() - 1;
- }
-
- int32_t attr_required_string = string_table.find("required");
- if (attr_required_string == -1) {
- string_table.push_back("required");
- attr_required_string = string_table.size() - 1;
- }
-
- for (int i = 0; i < feature_names.size(); i++) {
- String feature_name = feature_names[i];
- bool feature_required = feature_required_list[i];
- int feature_version = feature_versions[i];
- bool has_version_attribute = feature_version != -1;
-
- print_line("Adding feature " + feature_name);
-
- int32_t feature_string = string_table.find(feature_name);
- if (feature_string == -1) {
- string_table.push_back(feature_name);
- feature_string = string_table.size() - 1;
- }
-
- String required_value_string = feature_required ? "true" : "false";
- int32_t required_value = string_table.find(required_value_string);
- if (required_value == -1) {
- string_table.push_back(required_value_string);
- required_value = string_table.size() - 1;
- }
-
- int32_t attr_version_string = -1;
- int32_t version_value = -1;
- int tag_size;
- int attr_count;
- if (has_version_attribute) {
- attr_version_string = string_table.find("version");
- if (attr_version_string == -1) {
- string_table.push_back("version");
- attr_version_string = string_table.size() - 1;
- }
-
- version_value = string_table.find(itos(feature_version));
- if (version_value == -1) {
- string_table.push_back(itos(feature_version));
- version_value = string_table.size() - 1;
- }
-
- tag_size = 96; // node and three attrs + end node
- attr_count = 3;
- } else {
- tag_size = 76; // node and two attrs + end node
- attr_count = 2;
- }
- manifest_cur_size += tag_size + 24;
- p_manifest.resize(manifest_cur_size);
-
- // start tag
- encode_uint16(0x102, &p_manifest.write[ofs]); // type
- encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
- encode_uint32(tag_size, &p_manifest.write[ofs + 4]); // size
- encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
- encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
- encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
- encode_uint32(attr_uses_feature_string, &p_manifest.write[ofs + 20]); // name
- encode_uint16(20, &p_manifest.write[ofs + 24]); // attr_start
- encode_uint16(20, &p_manifest.write[ofs + 26]); // attr_size
- encode_uint16(attr_count, &p_manifest.write[ofs + 28]); // num_attrs
- encode_uint16(0, &p_manifest.write[ofs + 30]); // id_index
- encode_uint16(0, &p_manifest.write[ofs + 32]); // class_index
- encode_uint16(0, &p_manifest.write[ofs + 34]); // style_index
-
- // android:name attribute
- encode_uint32(ns_android_string, &p_manifest.write[ofs + 36]); // ns
- encode_uint32(attr_name_string, &p_manifest.write[ofs + 40]); // 'name'
- encode_uint32(feature_string, &p_manifest.write[ofs + 44]); // raw_value
- encode_uint16(8, &p_manifest.write[ofs + 48]); // typedvalue_size
- p_manifest.write[ofs + 50] = 0; // typedvalue_always0
- p_manifest.write[ofs + 51] = 0x03; // typedvalue_type (string)
- encode_uint32(feature_string, &p_manifest.write[ofs + 52]); // typedvalue reference
-
- // android:required attribute
- encode_uint32(ns_android_string, &p_manifest.write[ofs + 56]); // ns
- encode_uint32(attr_required_string, &p_manifest.write[ofs + 60]); // 'name'
- encode_uint32(required_value, &p_manifest.write[ofs + 64]); // raw_value
- encode_uint16(8, &p_manifest.write[ofs + 68]); // typedvalue_size
- p_manifest.write[ofs + 70] = 0; // typedvalue_always0
- p_manifest.write[ofs + 71] = 0x03; // typedvalue_type (string)
- encode_uint32(required_value, &p_manifest.write[ofs + 72]); // typedvalue reference
-
- ofs += 76;
-
- if (has_version_attribute) {
- // android:version attribute
- encode_uint32(ns_android_string, &p_manifest.write[ofs]); // ns
- encode_uint32(attr_version_string, &p_manifest.write[ofs + 4]); // 'name'
- encode_uint32(version_value, &p_manifest.write[ofs + 8]); // raw_value
- encode_uint16(8, &p_manifest.write[ofs + 12]); // typedvalue_size
- p_manifest.write[ofs + 14] = 0; // typedvalue_always0
- p_manifest.write[ofs + 15] = 0x03; // typedvalue_type (string)
- encode_uint32(version_value, &p_manifest.write[ofs + 16]); // typedvalue reference
-
- ofs += 20;
- }
-
- // end tag
- encode_uint16(0x103, &p_manifest.write[ofs]); // type
- encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
- encode_uint32(24, &p_manifest.write[ofs + 4]); // size
- encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
- encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
- encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
- encode_uint32(attr_uses_feature_string, &p_manifest.write[ofs + 20]); // name
-
- ofs += 24;
- }
- memcpy(&p_manifest.write[ofs], manifest_end.ptr(), manifest_end.size());
- ofs -= 24; // go back over back end
- }
- }
- if (tname == "manifest") {
- // save manifest ending so we can restore it
- Vector<uint8_t> manifest_end;
- uint32_t manifest_cur_size = p_manifest.size();
-
- manifest_end.resize(p_manifest.size() - ofs);
- memcpy(manifest_end.ptrw(), &p_manifest[ofs], manifest_end.size());
-
- int32_t attr_name_string = string_table.find("name");
- ERR_FAIL_COND_MSG(attr_name_string == -1, "Template does not have 'name' attribute.");
-
- int32_t ns_android_string = string_table.find("android");
- ERR_FAIL_COND_MSG(ns_android_string == -1, "Template does not have 'android' namespace.");
-
- int32_t attr_uses_permission_string = string_table.find("uses-permission");
- if (attr_uses_permission_string == -1) {
- string_table.push_back("uses-permission");
- attr_uses_permission_string = string_table.size() - 1;
- }
-
- for (int i = 0; i < perms.size(); ++i) {
- print_line("Adding permission " + perms[i]);
-
- manifest_cur_size += 56 + 24; // node + end node
- p_manifest.resize(manifest_cur_size);
-
- // Add permission to the string pool
- int32_t perm_string = string_table.find(perms[i]);
- if (perm_string == -1) {
- string_table.push_back(perms[i]);
- perm_string = string_table.size() - 1;
- }
-
- // start tag
- encode_uint16(0x102, &p_manifest.write[ofs]); // type
- encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
- encode_uint32(56, &p_manifest.write[ofs + 4]); // size
- encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
- encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
- encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
- encode_uint32(attr_uses_permission_string, &p_manifest.write[ofs + 20]); // name
- encode_uint16(20, &p_manifest.write[ofs + 24]); // attr_start
- encode_uint16(20, &p_manifest.write[ofs + 26]); // attr_size
- encode_uint16(1, &p_manifest.write[ofs + 28]); // num_attrs
- encode_uint16(0, &p_manifest.write[ofs + 30]); // id_index
- encode_uint16(0, &p_manifest.write[ofs + 32]); // class_index
- encode_uint16(0, &p_manifest.write[ofs + 34]); // style_index
-
- // attribute
- encode_uint32(ns_android_string, &p_manifest.write[ofs + 36]); // ns
- encode_uint32(attr_name_string, &p_manifest.write[ofs + 40]); // 'name'
- encode_uint32(perm_string, &p_manifest.write[ofs + 44]); // raw_value
- encode_uint16(8, &p_manifest.write[ofs + 48]); // typedvalue_size
- p_manifest.write[ofs + 50] = 0; // typedvalue_always0
- p_manifest.write[ofs + 51] = 0x03; // typedvalue_type (string)
- encode_uint32(perm_string, &p_manifest.write[ofs + 52]); // typedvalue reference
-
- ofs += 56;
-
- // end tag
- encode_uint16(0x103, &p_manifest.write[ofs]); // type
- encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
- encode_uint32(24, &p_manifest.write[ofs + 4]); // size
- encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
- encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
- encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
- encode_uint32(attr_uses_permission_string, &p_manifest.write[ofs + 20]); // name
-
- ofs += 24;
- }
-
- // copy footer back in
- memcpy(&p_manifest.write[ofs], manifest_end.ptr(), manifest_end.size());
- }
- } break;
- }
-
- ofs += size;
- }
-
- //create new andriodmanifest binary
-
- Vector<uint8_t> ret;
- ret.resize(string_table_begins + string_table.size() * 4);
-
- for (uint32_t i = 0; i < string_table_begins; i++) {
- ret.write[i] = p_manifest[i];
- }
-
- ofs = 0;
- for (int i = 0; i < string_table.size(); i++) {
- encode_uint32(ofs, &ret.write[string_table_begins + i * 4]);
- ofs += string_table[i].length() * 2 + 2 + 2;
- }
-
- ret.resize(ret.size() + ofs);
- string_data_offset = ret.size() - ofs;
- uint8_t *chars = &ret.write[string_data_offset];
- for (int i = 0; i < string_table.size(); i++) {
- String s = string_table[i];
- encode_uint16(s.length(), chars);
- chars += 2;
- for (int j = 0; j < s.length(); j++) {
- encode_uint16(s[j], chars);
- chars += 2;
- }
- encode_uint16(0, chars);
- chars += 2;
- }
-
- for (int i = 0; i < stable_extra.size(); i++) {
- ret.push_back(stable_extra[i]);
- }
-
- //pad
- while (ret.size() % 4) {
- ret.push_back(0);
- }
-
- uint32_t new_stable_end = ret.size();
-
- uint32_t extra = (p_manifest.size() - string_table_ends);
- ret.resize(new_stable_end + extra);
- for (uint32_t i = 0; i < extra; i++) {
- ret.write[new_stable_end + i] = p_manifest[string_table_ends + i];
- }
-
- while (ret.size() % 4) {
- ret.push_back(0);
- }
- encode_uint32(ret.size(), &ret.write[4]); //update new file size
-
- encode_uint32(new_stable_end - 8, &ret.write[12]); //update new string table size
- encode_uint32(string_table.size(), &ret.write[16]); //update new number of strings
- encode_uint32(string_data_offset - 8, &ret.write[28]); //update new string data offset
-
- p_manifest = ret;
- }
-
- static String _parse_string(const uint8_t *p_bytes, bool p_utf8) {
- uint32_t offset = 0;
- uint32_t len = 0;
-
- if (p_utf8) {
- uint8_t byte = p_bytes[offset];
- if (byte & 0x80) {
- offset += 2;
- } else {
- offset += 1;
- }
- byte = p_bytes[offset];
- offset++;
- if (byte & 0x80) {
- len = byte & 0x7F;
- len = (len << 8) + p_bytes[offset];
- offset++;
- } else {
- len = byte;
- }
- } else {
- len = decode_uint16(&p_bytes[offset]);
- offset += 2;
- if (len & 0x8000) {
- len &= 0x7FFF;
- len = (len << 16) + decode_uint16(&p_bytes[offset]);
- offset += 2;
- }
- }
-
- if (p_utf8) {
- Vector<uint8_t> str8;
- str8.resize(len + 1);
- for (uint32_t i = 0; i < len; i++) {
- str8.write[i] = p_bytes[offset + i];
- }
- str8.write[len] = 0;
- String str;
- str.parse_utf8((const char *)str8.ptr());
- return str;
- } else {
- String str;
- for (uint32_t i = 0; i < len; i++) {
- char32_t c = decode_uint16(&p_bytes[offset + i * 2]);
- if (c == 0) {
- break;
- }
- str += String::chr(c);
- }
- return str;
- }
- }
-
- void _fix_resources(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &r_manifest) {
- const int UTF8_FLAG = 0x00000100;
-
- uint32_t string_block_len = decode_uint32(&r_manifest[16]);
- uint32_t string_count = decode_uint32(&r_manifest[20]);
- uint32_t string_flags = decode_uint32(&r_manifest[28]);
- const uint32_t string_table_begins = 40;
-
- Vector<String> string_table;
-
- String package_name = p_preset->get("package/name");
-
- for (uint32_t i = 0; i < string_count; i++) {
- uint32_t offset = decode_uint32(&r_manifest[string_table_begins + i * 4]);
- offset += string_table_begins + string_count * 4;
-
- String str = _parse_string(&r_manifest[offset], string_flags & UTF8_FLAG);
-
- if (str.begins_with("godot-project-name")) {
- if (str == "godot-project-name") {
- //project name
- str = get_project_name(package_name);
-
- } else {
- String lang = str.substr(str.rfind("-") + 1, str.length()).replace("-", "_");
- String prop = "application/config/name_" + lang;
- if (ProjectSettings::get_singleton()->has_setting(prop)) {
- str = ProjectSettings::get_singleton()->get(prop);
- } else {
- str = get_project_name(package_name);
- }
- }
- }
-
- string_table.push_back(str);
- }
-
- //write a new string table, but use 16 bits
- Vector<uint8_t> ret;
- ret.resize(string_table_begins + string_table.size() * 4);
-
- for (uint32_t i = 0; i < string_table_begins; i++) {
- ret.write[i] = r_manifest[i];
- }
-
- int ofs = 0;
- for (int i = 0; i < string_table.size(); i++) {
- encode_uint32(ofs, &ret.write[string_table_begins + i * 4]);
- ofs += string_table[i].length() * 2 + 2 + 2;
- }
-
- ret.resize(ret.size() + ofs);
- uint8_t *chars = &ret.write[ret.size() - ofs];
- for (int i = 0; i < string_table.size(); i++) {
- String s = string_table[i];
- encode_uint16(s.length(), chars);
- chars += 2;
- for (int j = 0; j < s.length(); j++) {
- encode_uint16(s[j], chars);
- chars += 2;
- }
- encode_uint16(0, chars);
- chars += 2;
- }
-
- //pad
- while (ret.size() % 4) {
- ret.push_back(0);
- }
-
- //change flags to not use utf8
- encode_uint32(string_flags & ~0x100, &ret.write[28]);
- //change length
- encode_uint32(ret.size() - 12, &ret.write[16]);
- //append the rest...
- int rest_from = 12 + string_block_len;
- int rest_to = ret.size();
- int rest_len = (r_manifest.size() - rest_from);
- ret.resize(ret.size() + (r_manifest.size() - rest_from));
- for (int i = 0; i < rest_len; i++) {
- ret.write[rest_to + i] = r_manifest[rest_from + i];
- }
- //finally update the size
- encode_uint32(ret.size(), &ret.write[4]);
-
- r_manifest = ret;
- //printf("end\n");
- }
-
- void _load_image_data(const Ref<Image> &p_splash_image, Vector<uint8_t> &p_data) {
- Vector<uint8_t> png_buffer;
- Error err = PNGDriverCommon::image_to_png(p_splash_image, png_buffer);
- if (err == OK) {
- p_data.resize(png_buffer.size());
- memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size());
- } else {
- String err_str = String("Failed to convert splash image to png.");
- WARN_PRINT(err_str.utf8().get_data());
- }
- }
-
- void _process_launcher_icons(const String &p_file_name, const Ref<Image> &p_source_image, int dimension, Vector<uint8_t> &p_data) {
- Ref<Image> working_image = p_source_image;
-
- if (p_source_image->get_width() != dimension || p_source_image->get_height() != dimension) {
- working_image = p_source_image->duplicate();
- working_image->resize(dimension, dimension, Image::Interpolation::INTERPOLATE_LANCZOS);
- }
-
- Vector<uint8_t> png_buffer;
- Error err = PNGDriverCommon::image_to_png(working_image, png_buffer);
- if (err == OK) {
- p_data.resize(png_buffer.size());
- memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size());
- } else {
- String err_str = String("Failed to convert resized icon (") + p_file_name + ") to png.";
- WARN_PRINT(err_str.utf8().get_data());
- }
- }
-
- void load_splash_refs(Ref<Image> &splash_image, Ref<Image> &splash_bg_color_image) {
- // TODO: Figure out how to handle remaining boot splash parameters (e.g: fullsize, filter)
- String project_splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image");
-
- if (!project_splash_path.empty()) {
- splash_image.instance();
- const Error err = ImageLoader::load_image(project_splash_path, splash_image);
- if (err) {
- splash_image.unref();
- }
- }
-
- if (splash_image.is_null()) {
- // Use the default
- splash_image = Ref<Image>(memnew(Image(boot_splash_png)));
- }
-
- // Setup the splash bg color
- bool bg_color_valid;
- Color bg_color = ProjectSettings::get_singleton()->get("application/boot_splash/bg_color", &bg_color_valid);
- if (!bg_color_valid) {
- bg_color = boot_splash_bg_color;
- }
-
- splash_bg_color_image.instance();
- splash_bg_color_image->create(splash_image->get_width(), splash_image->get_height(), false, splash_image->get_format());
- splash_bg_color_image->fill(bg_color);
- }
-
- void load_icon_refs(const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background) {
- String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon");
-
- icon.instance();
- foreground.instance();
- background.instance();
-
- // Regular icon: user selection -> project icon -> default.
- String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges();
- if (path.empty() || ImageLoader::load_image(path, icon) != OK) {
- ImageLoader::load_image(project_icon_path, icon);
- }
-
- // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default).
- path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges();
- if (path.empty() || ImageLoader::load_image(path, foreground) != OK) {
- foreground = icon;
- }
-
- // Adaptive background: user selection -> default.
- path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges();
- if (!path.empty()) {
- ImageLoader::load_image(path, background);
- }
- }
-
- void store_image(const LauncherIcon launcher_icon, const Vector<uint8_t> &data) {
- store_image(launcher_icon.export_path, data);
- }
-
- void store_image(const String &export_path, const Vector<uint8_t> &data) {
- String img_path = export_path.insert(0, "res://android/build/");
- store_file_at_path(img_path, data);
- }
-
- void _copy_icons_to_gradle_project(const Ref<EditorExportPreset> &p_preset,
- const Ref<Image> &splash_image,
- const Ref<Image> &splash_bg_color_image,
- const Ref<Image> &main_image,
- const Ref<Image> &foreground,
- const Ref<Image> &background) {
- // Store the splash image
- if (splash_image.is_valid() && !splash_image->empty()) {
- Vector<uint8_t> data;
- _load_image_data(splash_image, data);
- store_image(SPLASH_IMAGE_EXPORT_PATH, data);
- }
-
- // Store the splash bg color image
- if (splash_bg_color_image.is_valid() && !splash_bg_color_image->empty()) {
- Vector<uint8_t> data;
- _load_image_data(splash_bg_color_image, data);
- store_image(SPLASH_BG_COLOR_PATH, data);
- }
-
- // Prepare images to be resized for the icons. If some image ends up being uninitialized,
- // the default image from the export template will be used.
-
- for (int i = 0; i < icon_densities_count; ++i) {
- if (main_image.is_valid() && !main_image->empty()) {
- Vector<uint8_t> data;
- _process_launcher_icons(launcher_icons[i].export_path, main_image, launcher_icons[i].dimensions, data);
- store_image(launcher_icons[i], data);
- }
-
- if (foreground.is_valid() && !foreground->empty()) {
- Vector<uint8_t> data;
- _process_launcher_icons(launcher_adaptive_icon_foregrounds[i].export_path, foreground,
- launcher_adaptive_icon_foregrounds[i].dimensions, data);
- store_image(launcher_adaptive_icon_foregrounds[i], data);
- }
-
- if (background.is_valid() && !background->empty()) {
- Vector<uint8_t> data;
- _process_launcher_icons(launcher_adaptive_icon_backgrounds[i].export_path, background,
- launcher_adaptive_icon_backgrounds[i].dimensions, data);
- store_image(launcher_adaptive_icon_backgrounds[i], data);
- }
- }
- }
-
- static Vector<String> get_enabled_abis(const Ref<EditorExportPreset> &p_preset) {
- Vector<String> abis = get_abis();
- Vector<String> enabled_abis;
- for (int i = 0; i < abis.size(); ++i) {
- bool is_enabled = p_preset->get("architectures/" + abis[i]);
- if (is_enabled) {
- enabled_abis.push_back(abis[i]);
- }
- }
- return enabled_abis;
- }
-
-public:
- typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
-
-public:
- virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override {
- String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name");
- if (driver == "GLES2") {
- r_features->push_back("etc");
- }
- // FIXME: Review what texture formats are used for Vulkan.
- if (driver == "Vulkan") {
- r_features->push_back("etc2");
- }
-
- Vector<String> abis = get_enabled_abis(p_preset);
- for (int i = 0; i < abis.size(); ++i) {
- r_features->push_back(abis[i]);
- }
- }
-
- virtual void get_export_options(List<ExportOption> *r_options) override {
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/32_bits_framebuffer"), true));
- r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/xr_mode", PROPERTY_HINT_ENUM, "Regular,Oculus Mobile VR"), 0));
- r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/degrees_of_freedom", PROPERTY_HINT_ENUM, "None,3DOF and 6DOF,6DOF"), 0));
- r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking", PROPERTY_HINT_ENUM, "None,Optional,Required"), 0));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "xr_features/focus_awareness"), false));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), false));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_template/use_custom_build"), false));
- r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "custom_template/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), 0));
-
- Vector<PluginConfig> plugins_configs = get_plugins();
- for (int i = 0; i < plugins_configs.size(); i++) {
- print_verbose("Found Android plugin " + plugins_configs[i].name);
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + plugins_configs[i].name), false));
- }
- plugins_changed = false;
-
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0"));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname"));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/signed"), true));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/immersive_mode"), true));
- r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "screen/orientation", PROPERTY_HINT_ENUM, "Landscape,Portrait"), 0));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_small"), true));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_normal"), true));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_large"), true));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_xlarge"), true));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/opengl_debug"), false));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_icon_option, PROPERTY_HINT_FILE, "*.png"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_foreground_option, PROPERTY_HINT_FILE, "*.png"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_background_option, PROPERTY_HINT_FILE, "*.png"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_user"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "apk_expansion/enable"), false));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "apk_expansion/SALT"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "apk_expansion/public_key", PROPERTY_HINT_MULTILINE_TEXT), ""));
-
- Vector<String> abis = get_abis();
- for (int i = 0; i < abis.size(); ++i) {
- String abi = abis[i];
- bool is_default = (abi == "armeabi-v7a" || abi == "arm64-v8a");
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "architectures/" + abi), is_default));
- }
-
- r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "permissions/custom_permissions"), PackedStringArray()));
-
- const char **perms = android_perms;
- while (*perms) {
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "permissions/" + String(*perms).to_lower()), false));
- perms++;
- }
- }
-
- virtual String get_name() const override {
- return "Android";
- }
-
- virtual String get_os_name() const override {
- return "Android";
- }
-
- virtual Ref<Texture2D> get_logo() const override {
- return logo;
- }
-
- virtual bool should_update_export_options() override {
- bool export_options_changed = plugins_changed;
- if (export_options_changed) {
- // don't clear unless we're reporting true, to avoid race
- plugins_changed = false;
- }
- return export_options_changed;
- }
-
- virtual bool poll_export() override {
- bool dc = devices_changed;
- if (dc) {
- // don't clear unless we're reporting true, to avoid race
- devices_changed = false;
- }
- return dc;
- }
-
- virtual int get_options_count() const override {
- MutexLock lock(device_lock);
- return devices.size();
- }
-
- virtual String get_options_tooltip() const override {
- return TTR("Select device from the list");
- }
-
- virtual String get_option_label(int p_index) const override {
- ERR_FAIL_INDEX_V(p_index, devices.size(), "");
- MutexLock lock(device_lock);
- return devices[p_index].name;
- }
-
- virtual String get_option_tooltip(int p_index) const override {
- ERR_FAIL_INDEX_V(p_index, devices.size(), "");
- MutexLock lock(device_lock);
- String s = devices[p_index].description;
- if (devices.size() == 1) {
- // Tooltip will be:
- // Name
- // Description
- s = devices[p_index].name + "\n\n" + s;
- }
- return s;
- }
-
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override {
- ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER);
-
- String can_export_error;
- bool can_export_missing_templates;
- if (!can_export(p_preset, can_export_error, can_export_missing_templates)) {
- EditorNode::add_io_error(can_export_error);
- return ERR_UNCONFIGURED;
- }
-
- MutexLock lock(device_lock);
-
- EditorProgress ep("run", "Running on " + devices[p_device].name, 3);
-
- String adb = EditorSettings::get_singleton()->get("export/android/adb");
-
- // Export_temp APK.
- if (ep.step("Exporting APK...", 0)) {
- return ERR_SKIP;
- }
-
- const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
- const bool use_reverse = devices[p_device].api_level >= 21;
-
- if (use_reverse) {
- p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST;
- }
-
- String tmp_export_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport.apk");
-
-#define CLEANUP_AND_RETURN(m_err) \
- { \
- DirAccess::remove_file_or_error(tmp_export_path); \
- return m_err; \
- }
-
- // Export to temporary APK before sending to device.
- Error err = export_project(p_preset, true, tmp_export_path, p_debug_flags);
-
- if (err != OK) {
- CLEANUP_AND_RETURN(err);
- }
-
- List<String> args;
- int rv;
-
- bool remove_prev = p_preset->get("one_click_deploy/clear_previous_install");
- String version_name = p_preset->get("version/name");
- String package_name = p_preset->get("package/unique_name");
-
- if (remove_prev) {
- if (ep.step("Uninstalling...", 1)) {
- CLEANUP_AND_RETURN(ERR_SKIP);
- }
-
- print_line("Uninstalling previous version: " + devices[p_device].name);
-
- args.push_back("-s");
- args.push_back(devices[p_device].id);
- args.push_back("uninstall");
- args.push_back(get_package_name(package_name));
-
- err = OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv);
- }
-
- print_line("Installing to device (please wait...): " + devices[p_device].name);
- if (ep.step("Installing to device, please wait...", 2)) {
- CLEANUP_AND_RETURN(ERR_SKIP);
- }
-
- args.clear();
- args.push_back("-s");
- args.push_back(devices[p_device].id);
- args.push_back("install");
- args.push_back("-r");
- args.push_back(tmp_export_path);
-
- err = OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv);
- if (err || rv != 0) {
- EditorNode::add_io_error("Could not install to device.");
- CLEANUP_AND_RETURN(ERR_CANT_CREATE);
- }
-
- if (use_remote) {
- if (use_reverse) {
- static const char *const msg = "--- Device API >= 21; debugging over USB ---";
- EditorNode::get_singleton()->get_log()->add_message(msg, EditorLog::MSG_TYPE_EDITOR);
- print_line(String(msg).to_upper());
-
- args.clear();
- args.push_back("-s");
- args.push_back(devices[p_device].id);
- args.push_back("reverse");
- args.push_back("--remove-all");
- OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv);
-
- if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) {
- int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
- args.clear();
- args.push_back("-s");
- args.push_back(devices[p_device].id);
- args.push_back("reverse");
- args.push_back("tcp:" + itos(dbg_port));
- args.push_back("tcp:" + itos(dbg_port));
-
- OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv);
- print_line("Reverse result: " + itos(rv));
- }
-
- if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) {
- int fs_port = EditorSettings::get_singleton()->get("filesystem/file_server/port");
-
- args.clear();
- args.push_back("-s");
- args.push_back(devices[p_device].id);
- args.push_back("reverse");
- args.push_back("tcp:" + itos(fs_port));
- args.push_back("tcp:" + itos(fs_port));
-
- err = OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv);
- print_line("Reverse result2: " + itos(rv));
- }
- } else {
- static const char *const msg = "--- Device API < 21; debugging over Wi-Fi ---";
- EditorNode::get_singleton()->get_log()->add_message(msg, EditorLog::MSG_TYPE_EDITOR);
- print_line(String(msg).to_upper());
- }
- }
-
- if (ep.step("Running on device...", 3)) {
- CLEANUP_AND_RETURN(ERR_SKIP);
- }
- args.clear();
- args.push_back("-s");
- args.push_back(devices[p_device].id);
- args.push_back("shell");
- args.push_back("am");
- args.push_back("start");
- if ((bool)EditorSettings::get_singleton()->get("export/android/force_system_user") && devices[p_device].api_level >= 17) { // Multi-user introduced in Android 17
- args.push_back("--user");
- args.push_back("0");
- }
- args.push_back("-a");
- args.push_back("android.intent.action.MAIN");
- args.push_back("-n");
- args.push_back(get_package_name(package_name) + "/com.godot.game.GodotApp");
-
- err = OS::get_singleton()->execute(adb, args, true, nullptr, nullptr, &rv);
- if (err || rv != 0) {
- EditorNode::add_io_error("Could not execute on device.");
- CLEANUP_AND_RETURN(ERR_CANT_CREATE);
- }
-
- CLEANUP_AND_RETURN(OK);
-#undef CLEANUP_AND_RETURN
- }
-
- virtual Ref<Texture2D> get_run_icon() const override {
- return run_icon;
- }
-
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override {
- String err;
- bool valid = false;
-
- // Look for export templates (first official, and if defined custom templates).
-
- if (!bool(p_preset->get("custom_template/use_custom_build"))) {
- String template_err;
- bool dvalid = false;
- bool rvalid = false;
-
- if (p_preset->get("custom_template/debug") != "") {
- dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
- if (!dvalid) {
- template_err += TTR("Custom debug template not found.") + "\n";
- }
- } else {
- dvalid = exists_export_template("android_debug.apk", &template_err);
- }
-
- if (p_preset->get("custom_template/release") != "") {
- rvalid = FileAccess::exists(p_preset->get("custom_template/release"));
- if (!rvalid) {
- template_err += TTR("Custom release template not found.") + "\n";
- }
- } else {
- rvalid = exists_export_template("android_release.apk", &template_err);
- }
-
- valid = dvalid || rvalid;
- if (!valid) {
- err += template_err;
- }
- } else {
- valid = exists_export_template("android_source.zip", &err);
- }
- r_missing_templates = !valid;
-
- // Validate the rest of the configuration.
-
- String adb = EditorSettings::get_singleton()->get("export/android/adb");
-
- if (!FileAccess::exists(adb)) {
- valid = false;
- err += TTR("ADB executable not configured in the Editor Settings.") + "\n";
- }
-
- String js = EditorSettings::get_singleton()->get("export/android/jarsigner");
-
- if (!FileAccess::exists(js)) {
- valid = false;
- err += TTR("OpenJDK jarsigner not configured in the Editor Settings.") + "\n";
- }
-
- String dk = p_preset->get("keystore/debug");
-
- if (!FileAccess::exists(dk)) {
- dk = EditorSettings::get_singleton()->get("export/android/debug_keystore");
- if (!FileAccess::exists(dk)) {
- valid = false;
- err += TTR("Debug keystore not configured in the Editor Settings nor in the preset.") + "\n";
- }
- }
-
- String rk = p_preset->get("keystore/release");
-
- if (!rk.empty() && !FileAccess::exists(rk)) {
- valid = false;
- err += TTR("Release keystore incorrectly configured in the export preset.") + "\n";
- }
-
- if (bool(p_preset->get("custom_template/use_custom_build"))) {
- String sdk_path = EditorSettings::get_singleton()->get("export/android/custom_build_sdk_path");
- if (sdk_path == "") {
- err += TTR("Custom build requires a valid Android SDK path in Editor Settings.") + "\n";
- valid = false;
- } else {
- Error errn;
- DirAccessRef da = DirAccess::open(sdk_path.plus_file("platform-tools"), &errn);
- if (errn != OK) {
- err += TTR("Invalid Android SDK path for custom build in Editor Settings.") + "\n";
- valid = false;
- }
- }
-
- if (!FileAccess::exists("res://android/build/build.gradle")) {
- err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n";
- valid = false;
- }
- }
-
- bool apk_expansion = p_preset->get("apk_expansion/enable");
-
- if (apk_expansion) {
- String apk_expansion_pkey = p_preset->get("apk_expansion/public_key");
-
- if (apk_expansion_pkey == "") {
- valid = false;
-
- err += TTR("Invalid public key for APK expansion.") + "\n";
- }
- }
-
- String pn = p_preset->get("package/unique_name");
- String pn_err;
-
- if (!is_package_name_valid(get_package_name(pn), &pn_err)) {
- valid = false;
- err += TTR("Invalid package name:") + " " + pn_err + "\n";
- }
-
- String etc_error = test_etc2();
- if (etc_error != String()) {
- valid = false;
- err += etc_error;
- }
-
- // Ensure that `Use Custom Build` is enabled if a plugin is selected.
- String enabled_plugins_names = get_plugins_names(get_enabled_plugins(p_preset));
- bool custom_build_enabled = p_preset->get("custom_template/use_custom_build");
- if (!enabled_plugins_names.empty() && !custom_build_enabled) {
- valid = false;
- err += TTR("\"Use Custom Build\" must be enabled to use the plugins.");
- err += "\n";
- }
-
- // Validate the Xr features are properly populated
- int xr_mode_index = p_preset->get("xr_features/xr_mode");
- int degrees_of_freedom = p_preset->get("xr_features/degrees_of_freedom");
- int hand_tracking = p_preset->get("xr_features/hand_tracking");
- bool focus_awareness = p_preset->get("xr_features/focus_awareness");
- if (xr_mode_index != /* XRMode.OVR*/ 1) {
- if (degrees_of_freedom > 0) {
- valid = false;
- err += TTR("\"Degrees Of Freedom\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\".");
- err += "\n";
- }
-
- if (hand_tracking > 0) {
- valid = false;
- err += TTR("\"Hand Tracking\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\".");
- err += "\n";
- }
-
- if (focus_awareness) {
- valid = false;
- err += TTR("\"Focus Awareness\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\".");
- err += "\n";
- }
- }
-
- if (int(p_preset->get("custom_template/export_format")) == 1 && /*AAB*/
- !bool(p_preset->get("custom_template/use_custom_build"))) {
- valid = false;
- err += TTR("\"Export AAB\" is only valid when \"Use Custom Build\" is enabled.");
- err += "\n";
- }
-
- r_error = err;
- return valid;
- }
-
- virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {
- List<String> list;
- list.push_back("apk");
- list.push_back("aab");
- return list;
- }
-
- inline bool is_clean_build_required(Vector<PluginConfig> enabled_plugins) {
- String plugin_names = get_plugins_names(enabled_plugins);
- bool first_build = last_custom_build_time == 0;
- bool have_plugins_changed = false;
-
- if (!first_build) {
- have_plugins_changed = plugin_names != last_plugin_names;
- if (!have_plugins_changed) {
- for (int i = 0; i < enabled_plugins.size(); i++) {
- if (enabled_plugins.get(i).last_updated > last_custom_build_time) {
- have_plugins_changed = true;
- break;
- }
- }
- }
- }
-
- last_custom_build_time = OS::get_singleton()->get_unix_time();
- last_plugin_names = plugin_names;
-
- return have_plugins_changed || first_build;
- }
-
- String get_apk_expansion_fullpath(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
- int version_code = p_preset->get("version/code");
- String package_name = p_preset->get("package/unique_name");
- String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb";
- String fullpath = p_path.get_base_dir().plus_file(apk_file_name);
- return fullpath;
- }
-
- Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
- String fullpath = get_apk_expansion_fullpath(p_preset, p_path);
- Error err = save_pack(p_preset, fullpath);
- return err;
- }
-
- void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) {
- String cmdline = p_preset->get("command_line/extra_args");
- Vector<String> command_line_strings = cmdline.strip_edges().split(" ");
- for (int i = 0; i < command_line_strings.size(); i++) {
- if (command_line_strings[i].strip_edges().length() == 0) {
- command_line_strings.remove(i);
- i--;
- }
- }
-
- gen_export_flags(command_line_strings, p_flags);
-
- bool apk_expansion = p_preset->get("apk_expansion/enable");
- if (apk_expansion) {
- String fullpath = get_apk_expansion_fullpath(p_preset, p_path);
- String apk_expansion_public_key = p_preset->get("apk_expansion/public_key");
-
- command_line_strings.push_back("--use_apk_expansion");
- command_line_strings.push_back("--apk_expansion_md5");
- command_line_strings.push_back(FileAccess::get_md5(fullpath));
- command_line_strings.push_back("--apk_expansion_key");
- command_line_strings.push_back(apk_expansion_public_key.strip_edges());
- }
-
- int xr_mode_index = p_preset->get("xr_features/xr_mode");
- if (xr_mode_index == 1) {
- command_line_strings.push_back("--xr_mode_ovr");
- } else { // XRMode.REGULAR is the default.
- command_line_strings.push_back("--xr_mode_regular");
- }
-
- bool use_32_bit_framebuffer = p_preset->get("graphics/32_bits_framebuffer");
- if (use_32_bit_framebuffer) {
- command_line_strings.push_back("--use_depth_32");
- }
-
- bool immersive = p_preset->get("screen/immersive_mode");
- if (immersive) {
- command_line_strings.push_back("--use_immersive");
- }
-
- bool debug_opengl = p_preset->get("screen/opengl_debug");
- if (debug_opengl) {
- command_line_strings.push_back("--debug_opengl");
- }
-
- if (command_line_strings.size()) {
- r_command_line_flags.resize(4);
- encode_uint32(command_line_strings.size(), &r_command_line_flags.write[0]);
- for (int i = 0; i < command_line_strings.size(); i++) {
- print_line(itos(i) + " param: " + command_line_strings[i]);
- CharString command_line_argument = command_line_strings[i].utf8();
- int base = r_command_line_flags.size();
- int length = command_line_argument.length();
- if (length == 0)
- continue;
- r_command_line_flags.resize(base + 4 + length);
- encode_uint32(length, &r_command_line_flags.write[base]);
- copymem(&r_command_line_flags.write[base + 4], command_line_argument.ptr(), length);
- }
- }
- }
-
- Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, String apk_path, EditorProgress ep) {
- String release_keystore = p_preset->get("keystore/release");
- String release_username = p_preset->get("keystore/release_user");
- String release_password = p_preset->get("keystore/release_password");
-
- String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner");
- if (!FileAccess::exists(jarsigner)) {
- EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting APK is unsigned.");
- return OK;
- }
-
- String keystore;
- String password;
- String user;
- if (p_debug) {
- keystore = p_preset->get("keystore/debug");
- password = p_preset->get("keystore/debug_password");
- user = p_preset->get("keystore/debug_user");
-
- if (keystore.empty()) {
- keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore");
- password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass");
- user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
- }
-
- if (ep.step("Signing debug APK...", 103)) {
- return ERR_SKIP;
- }
-
- } else {
- keystore = release_keystore;
- password = release_password;
- user = release_username;
-
- if (ep.step("Signing release APK...", 103)) {
- return ERR_SKIP;
- }
- }
-
- if (!FileAccess::exists(keystore)) {
- EditorNode::add_io_error("Could not find keystore, unable to export.");
- return ERR_FILE_CANT_OPEN;
- }
-
- List<String> args;
- args.push_back("-digestalg");
- args.push_back("SHA-256");
- args.push_back("-sigalg");
- args.push_back("SHA256withRSA");
- String tsa_url = EditorSettings::get_singleton()->get("export/android/timestamping_authority_url");
- if (tsa_url != "") {
- args.push_back("-tsa");
- args.push_back(tsa_url);
- }
- args.push_back("-verbose");
- args.push_back("-keystore");
- args.push_back(keystore);
- args.push_back("-storepass");
- args.push_back(password);
- args.push_back(apk_path);
- args.push_back(user);
- int retval;
- OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
- if (retval) {
- EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval));
- return ERR_CANT_CREATE;
- }
-
- if (ep.step("Verifying APK...", 104)) {
- return ERR_SKIP;
- }
-
- args.clear();
- args.push_back("-verify");
- args.push_back("-keystore");
- args.push_back(keystore);
- args.push_back(apk_path);
- args.push_back("-verbose");
-
- OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
- if (retval) {
- EditorNode::add_io_error("'jarsigner' verification of APK failed. Make sure to use a jarsigner from OpenJDK 8.");
- return ERR_CANT_CREATE;
- }
- return OK;
- }
-
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override {
- ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
-
- String src_apk;
- Error err;
-
- EditorProgress ep("export", "Exporting for Android", 105, true);
-
- bool use_custom_build = bool(p_preset->get("custom_template/use_custom_build"));
- int export_format = int(p_preset->get("custom_template/export_format"));
- bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG);
- bool _signed = p_preset->get("package/signed");
- bool apk_expansion = p_preset->get("apk_expansion/enable");
- Vector<String> enabled_abis = get_enabled_abis(p_preset);
-
- Ref<Image> splash_image;
- Ref<Image> splash_bg_color_image;
- load_splash_refs(splash_image, splash_bg_color_image);
-
- Ref<Image> main_image;
- Ref<Image> foreground;
- Ref<Image> background;
-
- load_icon_refs(p_preset, main_image, foreground, background);
-
- Vector<uint8_t> command_line_flags;
- // Write command line flags into the command_line_flags variable.
- get_command_line_flags(p_preset, p_path, p_flags, command_line_flags);
-
- if (export_format == 1) {
- if (!p_path.ends_with(".aab")) {
- EditorNode::get_singleton()->show_warning(TTR("Invalid filename! Android App Bundle requires the *.aab extension."));
- return ERR_UNCONFIGURED;
- }
- if (apk_expansion) {
- EditorNode::get_singleton()->show_warning(TTR("APK Expansion not compatible with Android App Bundle."));
- return ERR_UNCONFIGURED;
- }
- }
- if (export_format == 0 && !p_path.ends_with(".apk")) {
- EditorNode::get_singleton()->show_warning(
- TTR("Invalid filename! Android APK requires the *.apk extension."));
- return ERR_UNCONFIGURED;
- }
- if (export_format > 1 || export_format < 0) {
- EditorNode::add_io_error("Unsupported export format!\n");
- return ERR_UNCONFIGURED; //TODO: is this the right error?
- }
-
- if (use_custom_build) {
- //test that installed build version is alright
- FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ);
- if (!f) {
- EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu."));
- return ERR_UNCONFIGURED;
- }
- String version = f->get_line().strip_edges();
- if (version != VERSION_FULL_CONFIG) {
- EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG));
- return ERR_UNCONFIGURED;
- }
- String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path");
- ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/custom_build_sdk_path'.");
-
- // TODO: should we use "package/name" or "application/config/name"?
- String project_name = get_project_name(p_preset->get("package/name"));
- err = _create_project_name_strings_files(p_preset, project_name); //project name localization.
- if (err != OK) {
- EditorNode::add_io_error("Unable to overwrite res://android/build/res/*.xml files with project name");
- }
- // Copies the project icon files into the appropriate Gradle project directory.
- _copy_icons_to_gradle_project(p_preset, splash_image, splash_bg_color_image, main_image, foreground, background);
- // Write an AndroidManifest.xml file into the Gradle project directory.
- _write_tmp_manifest(p_preset, p_give_internet, p_debug);
-
- //stores all the project files inside the Gradle project directory. Also includes all ABIs
- if (!apk_expansion) {
- DirAccess *da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- if (da_res->dir_exists("res://android/build/assets")) {
- DirAccess *da_assets = DirAccess::open("res://android/build/assets");
- da_assets->erase_contents_recursive();
- da_res->remove("res://android/build/assets");
- }
- err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, NULL, ignore_so_file);
- if (err != OK) {
- EditorNode::add_io_error("Could not export project files to gradle project\n");
- return err;
- }
- } else {
- err = save_apk_expansion_file(p_preset, p_path);
- if (err != OK) {
- EditorNode::add_io_error("Could not write expansion package file!");
- return err;
- }
- }
- store_file_at_path("res://android/build/assets/_cl_", command_line_flags);
-
- OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required
- String build_command;
-
-#ifdef WINDOWS_ENABLED
- build_command = "gradlew.bat";
-#else
- build_command = "gradlew";
-#endif
-
- String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build");
- build_command = build_path.plus_file(build_command);
-
- String package_name = get_package_name(p_preset->get("package/unique_name"));
- String version_code = itos(p_preset->get("version/code"));
- String version_name = p_preset->get("version/name");
- String enabled_abi_string = String("|").join(enabled_abis);
-
- Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset);
- String local_plugins_binaries = get_plugins_binaries(BINARY_TYPE_LOCAL, enabled_plugins);
- String remote_plugins_binaries = get_plugins_binaries(BINARY_TYPE_REMOTE, enabled_plugins);
- String custom_maven_repos = get_plugins_custom_maven_repos(enabled_plugins);
- bool clean_build_required = is_clean_build_required(enabled_plugins);
-
- List<String> cmdline;
- if (clean_build_required) {
- cmdline.push_back("clean");
- }
-
- String build_type = p_debug ? "Debug" : "Release";
- if (export_format == 1) {
- String bundle_build_command = vformat("bundle%s", build_type);
- cmdline.push_back(bundle_build_command);
- } else if (export_format == 0) {
- String apk_build_command = vformat("assemble%s", build_type);
- cmdline.push_back(apk_build_command);
- }
-
- cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
- cmdline.push_back("-Pexport_version_code=" + version_code); // argument to specify the version code.
- cmdline.push_back("-Pexport_version_name=" + version_name); // argument to specify the version name.
- cmdline.push_back("-Pexport_enabled_abis=" + enabled_abi_string); // argument to specify enabled ABIs.
- cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies.
- cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies.
- cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies.
- cmdline.push_back("-p"); // argument to specify the start directory.
- cmdline.push_back(build_path); // start directory.
- /*{ used for debug
- int ec;
- String pipe;
- OS::get_singleton()->execute(build_command, cmdline, true, nullptr, nullptr, &ec);
- print_line("exit code: " + itos(ec));
- }
- */
- int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline);
- if (result != 0) {
- EditorNode::get_singleton()->show_warning(TTR("Building of Android project failed, check output for the error.\nAlternatively visit docs.godotengine.org for Android build documentation."));
- return ERR_CANT_CREATE;
- }
-
- List<String> copy_args;
- String copy_command;
- if (export_format == 1) {
- copy_command = vformat("copyAndRename%sAab", build_type);
- } else if (export_format == 0) {
- copy_command = vformat("copyAndRename%sApk", build_type);
- }
-
- copy_args.push_back(copy_command);
-
- copy_args.push_back("-p"); // argument to specify the start directory.
- copy_args.push_back(build_path); // start directory.
-
- String export_filename = p_path.get_file();
- String export_path = p_path.get_base_dir();
-
- copy_args.push_back("-Pexport_path=file:" + export_path);
- copy_args.push_back("-Pexport_filename=" + export_filename);
-
- int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args);
- if (copy_result != 0) {
- EditorNode::get_singleton()->show_warning(TTR("Unable to copy and rename export file, check gradle project directory for outputs."));
- return ERR_CANT_CREATE;
- }
- if (_signed) {
- err = sign_apk(p_preset, p_debug, p_path, ep);
- if (err != OK) {
- return err;
- }
- }
- return OK;
- }
- // This is the start of the Legacy build system
- if (p_debug)
- src_apk = p_preset->get("custom_template/debug");
- else
- src_apk = p_preset->get("custom_template/release");
- src_apk = src_apk.strip_edges();
- if (src_apk == "") {
- if (p_debug) {
- src_apk = find_export_template("android_debug.apk");
- } else {
- src_apk = find_export_template("android_release.apk");
- }
- if (src_apk == "") {
- EditorNode::add_io_error("Package not found: " + src_apk);
- return ERR_FILE_NOT_FOUND;
- }
- }
-
- if (!DirAccess::exists(p_path.get_base_dir())) {
- return ERR_FILE_BAD_PATH;
- }
-
- FileAccess *src_f = nullptr;
- zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
-
- if (ep.step("Creating APK...", 0)) {
- return ERR_SKIP;
- }
-
- unzFile pkg = unzOpen2(src_apk.utf8().get_data(), &io);
- if (!pkg) {
- EditorNode::add_io_error("Could not find template APK to export:\n" + src_apk);
- return ERR_FILE_NOT_FOUND;
- }
-
- int ret = unzGoToFirstFile(pkg);
-
- zlib_filefunc_def io2 = io;
- FileAccess *dst_f = nullptr;
- io2.opaque = &dst_f;
-
- String tmp_unaligned_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned.apk");
-
-#define CLEANUP_AND_RETURN(m_err) \
- { \
- DirAccess::remove_file_or_error(tmp_unaligned_path); \
- return m_err; \
- }
-
- zipFile unaligned_apk = zipOpen2(tmp_unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2);
-
- String cmdline = p_preset->get("command_line/extra_args");
-
- String version_name = p_preset->get("version/name");
- String package_name = p_preset->get("package/unique_name");
-
- String apk_expansion_pkey = p_preset->get("apk_expansion/public_key");
-
- Vector<String> invalid_abis(enabled_abis);
- while (ret == UNZ_OK) {
- //get filename
- unz_file_info info;
- char fname[16384];
- ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
-
- bool skip = false;
-
- String file = fname;
-
- Vector<uint8_t> data;
- data.resize(info.uncompressed_size);
-
- //read
- unzOpenCurrentFile(pkg);
- unzReadCurrentFile(pkg, data.ptrw(), data.size());
- unzCloseCurrentFile(pkg);
-
- //write
- if (file == "AndroidManifest.xml") {
- _fix_manifest(p_preset, data, p_give_internet);
- }
- if (file == "resources.arsc") {
- _fix_resources(p_preset, data);
- }
-
- // Process the splash image
- if (file == SPLASH_IMAGE_EXPORT_PATH && splash_image.is_valid() && !splash_image->empty()) {
- _load_image_data(splash_image, data);
- }
-
- // Process the splash bg color image
- if (file == SPLASH_BG_COLOR_PATH && splash_bg_color_image.is_valid() && !splash_bg_color_image->empty()) {
- _load_image_data(splash_bg_color_image, data);
- }
-
- for (int i = 0; i < icon_densities_count; ++i) {
- if (main_image.is_valid() && !main_image->empty()) {
- if (file == launcher_icons[i].export_path) {
- _process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data);
- }
- }
- if (foreground.is_valid() && !foreground->empty()) {
- if (file == launcher_adaptive_icon_foregrounds[i].export_path) {
- _process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data);
- }
- }
- if (background.is_valid() && !background->empty()) {
- if (file == launcher_adaptive_icon_backgrounds[i].export_path) {
- _process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data);
- }
- }
- }
-
- if (file.ends_with(".so")) {
- bool enabled = false;
- for (int i = 0; i < enabled_abis.size(); ++i) {
- if (file.begins_with("lib/" + enabled_abis[i] + "/")) {
- invalid_abis.erase(enabled_abis[i]);
- enabled = true;
- break;
- }
- }
- if (!enabled) {
- skip = true;
- }
- }
-
- if (file.begins_with("META-INF") && _signed) {
- skip = true;
- }
-
- if (!skip) {
- print_line("ADDING: " + file);
-
- // Respect decision on compression made by AAPT for the export template
- const bool uncompressed = info.compression_method == 0;
-
- zip_fileinfo zipfi = get_zip_fileinfo();
-
- zipOpenNewFileInZip(unaligned_apk,
- file.utf8().get_data(),
- &zipfi,
- nullptr,
- 0,
- nullptr,
- 0,
- nullptr,
- uncompressed ? 0 : Z_DEFLATED,
- Z_DEFAULT_COMPRESSION);
-
- zipWriteInFileInZip(unaligned_apk, data.ptr(), data.size());
- zipCloseFileInZip(unaligned_apk);
- }
-
- ret = unzGoToNextFile(pkg);
- }
-
- if (!invalid_abis.empty()) {
- String unsupported_arch = String(", ").join(invalid_abis);
- EditorNode::add_io_error("Missing libraries in the export template for the selected architectures: " + unsupported_arch + ".\n" +
- "Please build a template with all required libraries, or uncheck the missing architectures in the export preset.");
- CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND);
- }
-
- if (ep.step("Adding files...", 1)) {
- CLEANUP_AND_RETURN(ERR_SKIP);
- }
- err = OK;
-
- if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
- APKExportData ed;
- ed.ep = &ep;
- ed.apk = unaligned_apk;
- err = export_project_files(p_preset, ignore_apk_file, &ed, save_apk_so);
- } else {
- if (apk_expansion) {
- err = save_apk_expansion_file(p_preset, p_path);
- if (err != OK) {
- EditorNode::add_io_error("Could not write expansion package file!");
- return err;
- }
- } else {
- APKExportData ed;
- ed.ep = &ep;
- ed.apk = unaligned_apk;
- err = export_project_files(p_preset, save_apk_file, &ed, save_apk_so);
- }
- }
-
- if (err != OK) {
- unzClose(pkg);
- EditorNode::add_io_error("Could not export project files");
- CLEANUP_AND_RETURN(ERR_SKIP);
- }
-
- zip_fileinfo zipfi = get_zip_fileinfo();
- zipOpenNewFileInZip(unaligned_apk,
- "assets/_cl_",
- &zipfi,
- NULL,
- 0,
- NULL,
- 0,
- NULL,
- 0, // No compress (little size gain and potentially slower startup)
- Z_DEFAULT_COMPRESSION);
- zipWriteInFileInZip(unaligned_apk, command_line_flags.ptr(), command_line_flags.size());
- zipCloseFileInZip(unaligned_apk);
- zipClose(unaligned_apk, nullptr);
- unzClose(pkg);
-
- if (err != OK) {
- CLEANUP_AND_RETURN(err);
- }
-
- if (_signed) {
- err = sign_apk(p_preset, p_debug, tmp_unaligned_path, ep);
- if (err != OK) {
- CLEANUP_AND_RETURN(err);
- }
- }
-
- // Let's zip-align (must be done after signing)
-
- static const int ZIP_ALIGNMENT = 4;
-
- if (ep.step("Aligning APK...", 105)) {
- CLEANUP_AND_RETURN(ERR_SKIP);
- }
-
- unzFile tmp_unaligned = unzOpen2(tmp_unaligned_path.utf8().get_data(), &io);
- if (!tmp_unaligned) {
- EditorNode::add_io_error("Could not unzip temporary unaligned APK.");
- CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND);
- }
-
- ret = unzGoToFirstFile(tmp_unaligned);
-
- io2 = io;
- dst_f = nullptr;
- io2.opaque = &dst_f;
- zipFile final_apk = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2);
-
- // Take files from the unaligned APK and write them out to the aligned one
- // in raw mode, i.e. not uncompressing and recompressing, aligning them as needed,
- // following what is done in https://github.com/android/platform_build/blob/master/tools/zipalign/ZipAlign.cpp
- int bias = 0;
- while (ret == UNZ_OK) {
- unz_file_info info;
- memset(&info, 0, sizeof(info));
-
- char fname[16384];
- char extra[16384];
- ret = unzGetCurrentFileInfo(tmp_unaligned, &info, fname, 16384, extra, 16384 - ZIP_ALIGNMENT, nullptr, 0);
-
- String file = fname;
-
- Vector<uint8_t> data;
- data.resize(info.compressed_size);
-
- // read
- int method, level;
- unzOpenCurrentFile2(tmp_unaligned, &method, &level, 1); // raw read
- long file_offset = unzGetCurrentFileZStreamPos64(tmp_unaligned);
- unzReadCurrentFile(tmp_unaligned, data.ptrw(), data.size());
- unzCloseCurrentFile(tmp_unaligned);
-
- // align
- int padding = 0;
- if (!info.compression_method) {
- // Uncompressed file => Align
- long new_offset = file_offset + bias;
- padding = (ZIP_ALIGNMENT - (new_offset % ZIP_ALIGNMENT)) % ZIP_ALIGNMENT;
- }
-
- memset(extra + info.size_file_extra, 0, padding);
-
- zip_fileinfo fileinfo = get_zip_fileinfo();
- zipOpenNewFileInZip2(final_apk,
- file.utf8().get_data(),
- &fileinfo,
- extra,
- info.size_file_extra + padding,
- nullptr,
- 0,
- nullptr,
- method,
- level,
- 1); // raw write
- zipWriteInFileInZip(final_apk, data.ptr(), data.size());
- zipCloseFileInZipRaw(final_apk, info.uncompressed_size, info.crc);
-
- bias += padding;
-
- ret = unzGoToNextFile(tmp_unaligned);
- }
-
- zipClose(final_apk, nullptr);
- unzClose(tmp_unaligned);
-
- CLEANUP_AND_RETURN(OK);
- }
-
- virtual void get_platform_features(List<String> *r_features) override {
- r_features->push_back("mobile");
- r_features->push_back("Android");
- }
-
- virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override {
- }
-
- EditorExportPlatformAndroid() {
- Ref<Image> img = memnew(Image(_android_logo));
- logo.instance();
- logo->create_from_image(img);
-
- img = Ref<Image>(memnew(Image(_android_run_icon)));
- run_icon.instance();
- run_icon->create_from_image(img);
-
- devices_changed = true;
- plugins_changed = true;
- quit_request = false;
- check_for_changes_thread = Thread::create(_check_for_changes_poll_thread, this);
- }
-
- ~EditorExportPlatformAndroid() {
- quit_request = true;
- Thread::wait_to_finish(check_for_changes_thread);
- memdelete(check_for_changes_thread);
- }
-};
+#include "export_plugin.h"
void register_android_exporter() {
String exe_ext;
@@ -2773,21 +38,18 @@ void register_android_exporter() {
exe_ext = "*.exe";
}
- EDITOR_DEF("export/android/adb", "");
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/adb", PROPERTY_HINT_GLOBAL_FILE, exe_ext));
- EDITOR_DEF("export/android/jarsigner", "");
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/jarsigner", PROPERTY_HINT_GLOBAL_FILE, exe_ext));
+ EDITOR_DEF("export/android/android_sdk_path", "");
+ EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR));
EDITOR_DEF("export/android/debug_keystore", "");
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore"));
+ 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", "androiddebugkey");
EDITOR_DEF("export/android/debug_keystore_pass", "android");
EDITOR_DEF("export/android/force_system_user", false);
- EDITOR_DEF("export/android/custom_build_sdk_path", "");
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/custom_build_sdk_path", PROPERTY_HINT_GLOBAL_DIR));
- EDITOR_DEF("export/android/timestamping_authority_url", "");
EDITOR_DEF("export/android/shutdown_adb_on_exit", true);
+ EDITOR_DEF("export/android/one_click_deploy_clear_previous_install", false);
+
Ref<EditorExportPlatformAndroid> exporter = Ref<EditorExportPlatformAndroid>(memnew(EditorExportPlatformAndroid));
EditorExport::get_singleton()->add_export_platform(exporter);
}
diff --git a/platform/android/export/export.h b/platform/android/export/export.h
index d11ab9f49e..28e09f41db 100644
--- a/platform/android/export/export.h
+++ b/platform/android/export/export.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
new file mode 100644
index 0000000000..18e9e20948
--- /dev/null
+++ b/platform/android/export/export_plugin.cpp
@@ -0,0 +1,2978 @@
+/*************************************************************************/
+/* export_plugin.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "export_plugin.h"
+
+static const char *android_perms[] = {
+ "ACCESS_CHECKIN_PROPERTIES",
+ "ACCESS_COARSE_LOCATION",
+ "ACCESS_FINE_LOCATION",
+ "ACCESS_LOCATION_EXTRA_COMMANDS",
+ "ACCESS_MOCK_LOCATION",
+ "ACCESS_NETWORK_STATE",
+ "ACCESS_SURFACE_FLINGER",
+ "ACCESS_WIFI_STATE",
+ "ACCOUNT_MANAGER",
+ "ADD_VOICEMAIL",
+ "AUTHENTICATE_ACCOUNTS",
+ "BATTERY_STATS",
+ "BIND_ACCESSIBILITY_SERVICE",
+ "BIND_APPWIDGET",
+ "BIND_DEVICE_ADMIN",
+ "BIND_INPUT_METHOD",
+ "BIND_NFC_SERVICE",
+ "BIND_NOTIFICATION_LISTENER_SERVICE",
+ "BIND_PRINT_SERVICE",
+ "BIND_REMOTEVIEWS",
+ "BIND_TEXT_SERVICE",
+ "BIND_VPN_SERVICE",
+ "BIND_WALLPAPER",
+ "BLUETOOTH",
+ "BLUETOOTH_ADMIN",
+ "BLUETOOTH_PRIVILEGED",
+ "BRICK",
+ "BROADCAST_PACKAGE_REMOVED",
+ "BROADCAST_SMS",
+ "BROADCAST_STICKY",
+ "BROADCAST_WAP_PUSH",
+ "CALL_PHONE",
+ "CALL_PRIVILEGED",
+ "CAMERA",
+ "CAPTURE_AUDIO_OUTPUT",
+ "CAPTURE_SECURE_VIDEO_OUTPUT",
+ "CAPTURE_VIDEO_OUTPUT",
+ "CHANGE_COMPONENT_ENABLED_STATE",
+ "CHANGE_CONFIGURATION",
+ "CHANGE_NETWORK_STATE",
+ "CHANGE_WIFI_MULTICAST_STATE",
+ "CHANGE_WIFI_STATE",
+ "CLEAR_APP_CACHE",
+ "CLEAR_APP_USER_DATA",
+ "CONTROL_LOCATION_UPDATES",
+ "DELETE_CACHE_FILES",
+ "DELETE_PACKAGES",
+ "DEVICE_POWER",
+ "DIAGNOSTIC",
+ "DISABLE_KEYGUARD",
+ "DUMP",
+ "EXPAND_STATUS_BAR",
+ "FACTORY_TEST",
+ "FLASHLIGHT",
+ "FORCE_BACK",
+ "GET_ACCOUNTS",
+ "GET_PACKAGE_SIZE",
+ "GET_TASKS",
+ "GET_TOP_ACTIVITY_INFO",
+ "GLOBAL_SEARCH",
+ "HARDWARE_TEST",
+ "INJECT_EVENTS",
+ "INSTALL_LOCATION_PROVIDER",
+ "INSTALL_PACKAGES",
+ "INSTALL_SHORTCUT",
+ "INTERNAL_SYSTEM_WINDOW",
+ "INTERNET",
+ "KILL_BACKGROUND_PROCESSES",
+ "LOCATION_HARDWARE",
+ "MANAGE_ACCOUNTS",
+ "MANAGE_APP_TOKENS",
+ "MANAGE_DOCUMENTS",
+ "MASTER_CLEAR",
+ "MEDIA_CONTENT_CONTROL",
+ "MODIFY_AUDIO_SETTINGS",
+ "MODIFY_PHONE_STATE",
+ "MOUNT_FORMAT_FILESYSTEMS",
+ "MOUNT_UNMOUNT_FILESYSTEMS",
+ "NFC",
+ "PERSISTENT_ACTIVITY",
+ "PROCESS_OUTGOING_CALLS",
+ "READ_CALENDAR",
+ "READ_CALL_LOG",
+ "READ_CONTACTS",
+ "READ_EXTERNAL_STORAGE",
+ "READ_FRAME_BUFFER",
+ "READ_HISTORY_BOOKMARKS",
+ "READ_INPUT_STATE",
+ "READ_LOGS",
+ "READ_PHONE_STATE",
+ "READ_PROFILE",
+ "READ_SMS",
+ "READ_SOCIAL_STREAM",
+ "READ_SYNC_SETTINGS",
+ "READ_SYNC_STATS",
+ "READ_USER_DICTIONARY",
+ "REBOOT",
+ "RECEIVE_BOOT_COMPLETED",
+ "RECEIVE_MMS",
+ "RECEIVE_SMS",
+ "RECEIVE_WAP_PUSH",
+ "RECORD_AUDIO",
+ "REORDER_TASKS",
+ "RESTART_PACKAGES",
+ "SEND_RESPOND_VIA_MESSAGE",
+ "SEND_SMS",
+ "SET_ACTIVITY_WATCHER",
+ "SET_ALARM",
+ "SET_ALWAYS_FINISH",
+ "SET_ANIMATION_SCALE",
+ "SET_DEBUG_APP",
+ "SET_ORIENTATION",
+ "SET_POINTER_SPEED",
+ "SET_PREFERRED_APPLICATIONS",
+ "SET_PROCESS_LIMIT",
+ "SET_TIME",
+ "SET_TIME_ZONE",
+ "SET_WALLPAPER",
+ "SET_WALLPAPER_HINTS",
+ "SIGNAL_PERSISTENT_PROCESSES",
+ "STATUS_BAR",
+ "SUBSCRIBED_FEEDS_READ",
+ "SUBSCRIBED_FEEDS_WRITE",
+ "SYSTEM_ALERT_WINDOW",
+ "TRANSMIT_IR",
+ "UNINSTALL_SHORTCUT",
+ "UPDATE_DEVICE_STATS",
+ "USE_CREDENTIALS",
+ "USE_SIP",
+ "VIBRATE",
+ "WAKE_LOCK",
+ "WRITE_APN_SETTINGS",
+ "WRITE_CALENDAR",
+ "WRITE_CALL_LOG",
+ "WRITE_CONTACTS",
+ "WRITE_EXTERNAL_STORAGE",
+ "WRITE_GSERVICES",
+ "WRITE_HISTORY_BOOKMARKS",
+ "WRITE_PROFILE",
+ "WRITE_SECURE_SETTINGS",
+ "WRITE_SETTINGS",
+ "WRITE_SMS",
+ "WRITE_SOCIAL_STREAM",
+ "WRITE_SYNC_SETTINGS",
+ "WRITE_USER_DICTIONARY",
+ nullptr
+};
+
+static const char *SPLASH_IMAGE_EXPORT_PATH = "res/drawable-nodpi/splash.png";
+static const char *LEGACY_BUILD_SPLASH_IMAGE_EXPORT_PATH = "res/drawable-nodpi-v4/splash.png";
+static const char *SPLASH_BG_COLOR_PATH = "res/drawable-nodpi/splash_bg_color.png";
+static const char *LEGACY_BUILD_SPLASH_BG_COLOR_PATH = "res/drawable-nodpi-v4/splash_bg_color.png";
+static const char *SPLASH_CONFIG_PATH = "res://android/build/res/drawable/splash_drawable.xml";
+static const char *GDNATIVE_LIBS_PATH = "res://android/build/libs/gdnativelibs.json";
+
+static const int icon_densities_count = 6;
+static const char *launcher_icon_option = "launcher_icons/main_192x192";
+static const char *launcher_adaptive_icon_foreground_option = "launcher_icons/adaptive_foreground_432x432";
+static const char *launcher_adaptive_icon_background_option = "launcher_icons/adaptive_background_432x432";
+
+static const LauncherIcon launcher_icons[icon_densities_count] = {
+ { "res/mipmap-xxxhdpi-v4/icon.png", 192 },
+ { "res/mipmap-xxhdpi-v4/icon.png", 144 },
+ { "res/mipmap-xhdpi-v4/icon.png", 96 },
+ { "res/mipmap-hdpi-v4/icon.png", 72 },
+ { "res/mipmap-mdpi-v4/icon.png", 48 },
+ { "res/mipmap/icon.png", 192 }
+};
+
+static const LauncherIcon launcher_adaptive_icon_foregrounds[icon_densities_count] = {
+ { "res/mipmap-xxxhdpi-v4/icon_foreground.png", 432 },
+ { "res/mipmap-xxhdpi-v4/icon_foreground.png", 324 },
+ { "res/mipmap-xhdpi-v4/icon_foreground.png", 216 },
+ { "res/mipmap-hdpi-v4/icon_foreground.png", 162 },
+ { "res/mipmap-mdpi-v4/icon_foreground.png", 108 },
+ { "res/mipmap/icon_foreground.png", 432 }
+};
+
+static const LauncherIcon launcher_adaptive_icon_backgrounds[icon_densities_count] = {
+ { "res/mipmap-xxxhdpi-v4/icon_background.png", 432 },
+ { "res/mipmap-xxhdpi-v4/icon_background.png", 324 },
+ { "res/mipmap-xhdpi-v4/icon_background.png", 216 },
+ { "res/mipmap-hdpi-v4/icon_background.png", 162 },
+ { "res/mipmap-mdpi-v4/icon_background.png", 108 },
+ { "res/mipmap/icon_background.png", 432 }
+};
+
+static const int EXPORT_FORMAT_APK = 0;
+static const int EXPORT_FORMAT_AAB = 1;
+
+static const char *APK_ASSETS_DIRECTORY = "res://android/build/assets";
+static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/installTime/src/main/assets";
+
+void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
+ EditorExportPlatformAndroid *ea = (EditorExportPlatformAndroid *)ud;
+
+ while (!ea->quit_request.is_set()) {
+ // Check for plugins updates
+ {
+ // Nothing to do if we already know the plugins have changed.
+ if (!ea->plugins_changed.is_set()) {
+ Vector<PluginConfigAndroid> loaded_plugins = get_plugins();
+
+ MutexLock lock(ea->plugins_lock);
+
+ if (ea->plugins.size() != loaded_plugins.size()) {
+ ea->plugins_changed.set();
+ } else {
+ for (int i = 0; i < ea->plugins.size(); i++) {
+ if (ea->plugins[i].name != loaded_plugins[i].name) {
+ ea->plugins_changed.set();
+ break;
+ }
+ }
+ }
+
+ if (ea->plugins_changed.is_set()) {
+ ea->plugins = loaded_plugins;
+ }
+ }
+ }
+
+ // Check for devices updates
+ String adb = get_adb_path();
+ if (FileAccess::exists(adb)) {
+ String devices;
+ List<String> args;
+ args.push_back("devices");
+ int ec;
+ OS::get_singleton()->execute(adb, args, &devices, &ec);
+
+ Vector<String> ds = devices.split("\n");
+ Vector<String> ldevices;
+ for (int i = 1; i < ds.size(); i++) {
+ String d = ds[i];
+ int dpos = d.find("device");
+ if (dpos == -1) {
+ continue;
+ }
+ d = d.substr(0, dpos).strip_edges();
+ ldevices.push_back(d);
+ }
+
+ MutexLock lock(ea->device_lock);
+
+ bool different = false;
+
+ if (ea->devices.size() != ldevices.size()) {
+ different = true;
+ } else {
+ for (int i = 0; i < ea->devices.size(); i++) {
+ if (ea->devices[i].id != ldevices[i]) {
+ different = true;
+ break;
+ }
+ }
+ }
+
+ if (different) {
+ Vector<Device> ndevices;
+
+ for (int i = 0; i < ldevices.size(); i++) {
+ Device d;
+ d.id = ldevices[i];
+ for (int j = 0; j < ea->devices.size(); j++) {
+ if (ea->devices[j].id == ldevices[i]) {
+ d.description = ea->devices[j].description;
+ d.name = ea->devices[j].name;
+ d.api_level = ea->devices[j].api_level;
+ }
+ }
+
+ if (d.description == "") {
+ //in the oven, request!
+ args.clear();
+ args.push_back("-s");
+ args.push_back(d.id);
+ args.push_back("shell");
+ args.push_back("getprop");
+ int ec2;
+ String dp;
+
+ OS::get_singleton()->execute(adb, args, &dp, &ec2);
+
+ Vector<String> props = dp.split("\n");
+ String vendor;
+ String device;
+ d.description = "Device ID: " + d.id + "\n";
+ d.api_level = 0;
+ for (int j = 0; j < props.size(); j++) {
+ // got information by `shell cat /system/build.prop` before and its format is "property=value"
+ // it's now changed to use `shell getporp` because of permission issue with Android 8.0 and above
+ // its format is "[property]: [value]" so changed it as like build.prop
+ String p = props[j];
+ p = p.replace("]: ", "=");
+ p = p.replace("[", "");
+ p = p.replace("]", "");
+
+ if (p.begins_with("ro.product.model=")) {
+ device = p.get_slice("=", 1).strip_edges();
+ } else if (p.begins_with("ro.product.brand=")) {
+ vendor = p.get_slice("=", 1).strip_edges().capitalize();
+ } else if (p.begins_with("ro.build.display.id=")) {
+ d.description += "Build: " + p.get_slice("=", 1).strip_edges() + "\n";
+ } else if (p.begins_with("ro.build.version.release=")) {
+ d.description += "Release: " + p.get_slice("=", 1).strip_edges() + "\n";
+ } else if (p.begins_with("ro.build.version.sdk=")) {
+ d.api_level = p.get_slice("=", 1).to_int();
+ } else if (p.begins_with("ro.product.cpu.abi=")) {
+ d.description += "CPU: " + p.get_slice("=", 1).strip_edges() + "\n";
+ } else if (p.begins_with("ro.product.manufacturer=")) {
+ d.description += "Manufacturer: " + p.get_slice("=", 1).strip_edges() + "\n";
+ } else if (p.begins_with("ro.board.platform=")) {
+ d.description += "Chipset: " + p.get_slice("=", 1).strip_edges() + "\n";
+ } else if (p.begins_with("ro.opengles.version=")) {
+ uint32_t opengl = p.get_slice("=", 1).to_int();
+ d.description += "OpenGL: " + itos(opengl >> 16) + "." + itos((opengl >> 8) & 0xFF) + "." + itos((opengl)&0xFF) + "\n";
+ }
+ }
+
+ d.name = vendor + " " + device;
+ if (device == String()) {
+ continue;
+ }
+ }
+
+ ndevices.push_back(d);
+ }
+
+ ea->devices = ndevices;
+ ea->devices_changed.set();
+ }
+ }
+
+ uint64_t sleep = 200;
+ uint64_t wait = 3000000;
+ uint64_t time = OS::get_singleton()->get_ticks_usec();
+ while (OS::get_singleton()->get_ticks_usec() - time < wait) {
+ OS::get_singleton()->delay_usec(1000 * sleep);
+ if (ea->quit_request.is_set()) {
+ break;
+ }
+ }
+ }
+
+ if (EditorSettings::get_singleton()->get("export/android/shutdown_adb_on_exit")) {
+ String adb = get_adb_path();
+ if (!FileAccess::exists(adb)) {
+ return; //adb not configured
+ }
+
+ List<String> args;
+ args.push_back("kill-server");
+ OS::get_singleton()->execute(adb, args);
+ };
+}
+
+String EditorExportPlatformAndroid::get_project_name(const String &p_name) const {
+ String aname;
+ if (p_name != "") {
+ aname = p_name;
+ } else {
+ aname = ProjectSettings::get_singleton()->get("application/config/name");
+ }
+
+ if (aname == "") {
+ aname = VERSION_NAME;
+ }
+
+ return aname;
+}
+
+String EditorExportPlatformAndroid::get_package_name(const String &p_package) const {
+ String pname = p_package;
+ String basename = ProjectSettings::get_singleton()->get("application/config/name");
+ basename = basename.to_lower();
+
+ String name;
+ bool first = true;
+ for (int i = 0; i < basename.length(); i++) {
+ char32_t c = basename[i];
+ if (c >= '0' && c <= '9' && first) {
+ continue;
+ }
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
+ name += String::chr(c);
+ first = false;
+ }
+ }
+ if (name == "") {
+ name = "noname";
+ }
+
+ pname = pname.replace("$genname", name);
+
+ return pname;
+}
+
+String EditorExportPlatformAndroid::get_assets_directory(const Ref<EditorExportPreset> &p_preset, int p_export_format) const {
+ return p_export_format == EXPORT_FORMAT_AAB ? AAB_ASSETS_DIRECTORY : APK_ASSETS_DIRECTORY;
+}
+
+bool EditorExportPlatformAndroid::is_package_name_valid(const String &p_package, String *r_error) const {
+ String pname = p_package;
+
+ if (pname.length() == 0) {
+ if (r_error) {
+ *r_error = TTR("Package name is missing.");
+ }
+ return false;
+ }
+
+ int segments = 0;
+ bool first = true;
+ for (int i = 0; i < pname.length(); i++) {
+ char32_t c = pname[i];
+ if (first && c == '.') {
+ if (r_error) {
+ *r_error = TTR("Package segments must be of non-zero length.");
+ }
+ return false;
+ }
+ if (c == '.') {
+ segments++;
+ first = true;
+ continue;
+ }
+ if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) {
+ if (r_error) {
+ *r_error = vformat(TTR("The character '%s' is not allowed in Android application package names."), String::chr(c));
+ }
+ return false;
+ }
+ if (first && (c >= '0' && c <= '9')) {
+ if (r_error) {
+ *r_error = TTR("A digit cannot be the first character in a package segment.");
+ }
+ return false;
+ }
+ if (first && c == '_') {
+ if (r_error) {
+ *r_error = vformat(TTR("The character '%s' cannot be the first character in a package segment."), String::chr(c));
+ }
+ return false;
+ }
+ first = false;
+ }
+
+ if (segments == 0) {
+ if (r_error) {
+ *r_error = TTR("The package must have at least one '.' separator.");
+ }
+ return false;
+ }
+
+ if (first) {
+ if (r_error) {
+ *r_error = TTR("Package segments must be of non-zero length.");
+ }
+ return false;
+ }
+
+ return true;
+}
+
+bool EditorExportPlatformAndroid::_should_compress_asset(const String &p_path, const Vector<uint8_t> &p_data) {
+ /*
+ * By not compressing files with little or not benefit in doing so,
+ * a performance gain is expected attime. Moreover, if the APK is
+ * zip-aligned, assets stored as they are can be efficiently read by
+ * Android by memory-mapping them.
+ */
+
+ // -- Unconditional uncompress to mimic AAPT plus some other
+
+ static const char *unconditional_compress_ext[] = {
+ // From https://github.com/android/platform_frameworks_base/blob/master/tools/aapt/Package.cpp
+ // These formats are already compressed, or don't compress well:
+ ".jpg", ".jpeg", ".png", ".gif",
+ ".wav", ".mp2", ".mp3", ".ogg", ".aac",
+ ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
+ ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
+ ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
+ ".amr", ".awb", ".wma", ".wmv",
+ // Godot-specific:
+ ".webp", // Same reasoning as .png
+ ".cfb", // Don't let small config files slow-down startup
+ ".scn", // Binary scenes are usually already compressed
+ ".stex", // Streamable textures are usually already compressed
+ // Trailer for easier processing
+ nullptr
+ };
+
+ for (const char **ext = unconditional_compress_ext; *ext; ++ext) {
+ if (p_path.to_lower().ends_with(String(*ext))) {
+ return false;
+ }
+ }
+
+ // -- Compressed resource?
+
+ if (p_data.size() >= 4 && p_data[0] == 'R' && p_data[1] == 'S' && p_data[2] == 'C' && p_data[3] == 'C') {
+ // Already compressed
+ return false;
+ }
+
+ // --- TODO: Decide on texture resources according to their image compression setting
+
+ return true;
+}
+
+zip_fileinfo EditorExportPlatformAndroid::get_zip_fileinfo() {
+ OS::Time time = OS::get_singleton()->get_time();
+ OS::Date date = OS::get_singleton()->get_date();
+
+ zip_fileinfo zipfi;
+ zipfi.tmz_date.tm_hour = time.hour;
+ zipfi.tmz_date.tm_mday = date.day;
+ zipfi.tmz_date.tm_min = time.minute;
+ zipfi.tmz_date.tm_mon = date.month - 1; // tm_mon is zero indexed
+ zipfi.tmz_date.tm_sec = time.second;
+ zipfi.tmz_date.tm_year = date.year;
+ zipfi.dosDate = 0;
+ zipfi.external_fa = 0;
+ zipfi.internal_fa = 0;
+
+ return zipfi;
+}
+
+Vector<String> EditorExportPlatformAndroid::get_abis() {
+ Vector<String> abis;
+ abis.push_back("armeabi-v7a");
+ abis.push_back("arm64-v8a");
+ abis.push_back("x86");
+ abis.push_back("x86_64");
+ return abis;
+}
+
+/// List the gdap files in the directory specified by the p_path parameter.
+Vector<String> EditorExportPlatformAndroid::list_gdap_files(const String &p_path) {
+ Vector<String> dir_files;
+ DirAccessRef da = DirAccess::open(p_path);
+ if (da) {
+ da->list_dir_begin();
+ while (true) {
+ String file = da->get_next();
+ if (file == "") {
+ break;
+ }
+
+ if (da->current_is_dir() || da->current_is_hidden()) {
+ continue;
+ }
+
+ if (file.ends_with(PluginConfigAndroid::PLUGIN_CONFIG_EXT)) {
+ dir_files.push_back(file);
+ }
+ }
+ da->list_dir_end();
+ }
+
+ return dir_files;
+}
+
+Vector<PluginConfigAndroid> EditorExportPlatformAndroid::get_plugins() {
+ Vector<PluginConfigAndroid> loaded_plugins;
+
+ String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins");
+
+ // Add the prebuilt plugins
+ loaded_plugins.append_array(PluginConfigAndroid::get_prebuilt_plugins(plugins_dir));
+
+ if (DirAccess::exists(plugins_dir)) {
+ Vector<String> plugins_filenames = list_gdap_files(plugins_dir);
+
+ if (!plugins_filenames.is_empty()) {
+ Ref<ConfigFile> config_file = memnew(ConfigFile);
+ for (int i = 0; i < plugins_filenames.size(); i++) {
+ PluginConfigAndroid config = PluginConfigAndroid::load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i]));
+ if (config.valid_config) {
+ loaded_plugins.push_back(config);
+ } else {
+ print_error("Invalid plugin config file " + plugins_filenames[i]);
+ }
+ }
+ }
+ }
+
+ return loaded_plugins;
+}
+
+Vector<PluginConfigAndroid> EditorExportPlatformAndroid::get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) {
+ Vector<PluginConfigAndroid> enabled_plugins;
+ Vector<PluginConfigAndroid> all_plugins = get_plugins();
+ for (int i = 0; i < all_plugins.size(); i++) {
+ PluginConfigAndroid plugin = all_plugins[i];
+ bool enabled = p_presets->get("plugins/" + plugin.name);
+ if (enabled) {
+ enabled_plugins.push_back(plugin);
+ }
+ }
+
+ return enabled_plugins;
+}
+
+Error EditorExportPlatformAndroid::store_in_apk(APKExportData *ed, const String &p_path, const Vector<uint8_t> &p_data, int compression_method) {
+ zip_fileinfo zipfi = get_zip_fileinfo();
+ zipOpenNewFileInZip(ed->apk,
+ p_path.utf8().get_data(),
+ &zipfi,
+ nullptr,
+ 0,
+ nullptr,
+ 0,
+ nullptr,
+ compression_method,
+ Z_DEFAULT_COMPRESSION);
+
+ zipWriteInFileInZip(ed->apk, p_data.ptr(), p_data.size());
+ zipCloseFileInZip(ed->apk);
+
+ return OK;
+}
+
+Error EditorExportPlatformAndroid::save_apk_so(void *p_userdata, const SharedObject &p_so) {
+ if (!p_so.path.get_file().begins_with("lib")) {
+ String err = "Android .so file names must start with \"lib\", but got: " + p_so.path;
+ ERR_PRINT(err);
+ return FAILED;
+ }
+ APKExportData *ed = (APKExportData *)p_userdata;
+ Vector<String> abis = get_abis();
+ bool exported = false;
+ for (int i = 0; i < p_so.tags.size(); ++i) {
+ // shared objects can be fat (compatible with multiple ABIs)
+ int abi_index = abis.find(p_so.tags[i]);
+ if (abi_index != -1) {
+ exported = true;
+ String abi = abis[abi_index];
+ String dst_path = String("lib").plus_file(abi).plus_file(p_so.path.get_file());
+ Vector<uint8_t> array = FileAccess::get_file_as_array(p_so.path);
+ Error store_err = store_in_apk(ed, dst_path, array);
+ ERR_FAIL_COND_V_MSG(store_err, store_err, "Cannot store in apk file '" + dst_path + "'.");
+ }
+ }
+ if (!exported) {
+ String abis_string = String(" ").join(abis);
+ String err = "Cannot determine ABI for library \"" + p_so.path + "\". One of the supported ABIs must be used as a tag: " + abis_string;
+ ERR_PRINT(err);
+ return FAILED;
+ }
+ return OK;
+}
+
+Error EditorExportPlatformAndroid::save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
+ APKExportData *ed = (APKExportData *)p_userdata;
+ String dst_path = p_path.replace_first("res://", "assets/");
+
+ store_in_apk(ed, dst_path, p_data, _should_compress_asset(p_path, p_data) ? Z_DEFLATED : 0);
+ return OK;
+}
+
+Error EditorExportPlatformAndroid::ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
+ return OK;
+}
+
+Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const SharedObject &p_so) {
+ ERR_FAIL_COND_V_MSG(!p_so.path.get_file().begins_with("lib"), FAILED,
+ "Android .so file names must start with \"lib\", but got: " + p_so.path);
+ Vector<String> abis = get_abis();
+ CustomExportData *export_data = (CustomExportData *)p_userdata;
+ bool exported = false;
+ for (int i = 0; i < p_so.tags.size(); ++i) {
+ int abi_index = abis.find(p_so.tags[i]);
+ if (abi_index != -1) {
+ exported = true;
+ String base = "res://android/build/libs";
+ String type = export_data->debug ? "debug" : "release";
+ String abi = abis[abi_index];
+ String filename = p_so.path.get_file();
+ String dst_path = base.plus_file(type).plus_file(abi).plus_file(filename);
+ Vector<uint8_t> data = FileAccess::get_file_as_array(p_so.path);
+ print_verbose("Copying .so file from " + p_so.path + " to " + dst_path);
+ Error err = store_file_at_path(dst_path, data);
+ ERR_FAIL_COND_V_MSG(err, err, "Failed to copy .so file from " + p_so.path + " to " + dst_path);
+ export_data->libs.push_back(dst_path);
+ }
+ }
+ ERR_FAIL_COND_V_MSG(!exported, FAILED,
+ "Cannot determine ABI for library \"" + p_so.path + "\". One of the supported ABIs must be used as a tag: " + String(" ").join(abis));
+ return OK;
+}
+
+bool EditorExportPlatformAndroid::_has_storage_permission(const Vector<String> &p_permissions) {
+ return p_permissions.find("android.permission.READ_EXTERNAL_STORAGE") != -1 || p_permissions.find("android.permission.WRITE_EXTERNAL_STORAGE") != -1;
+}
+
+void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) {
+ const char **aperms = android_perms;
+ while (*aperms) {
+ bool enabled = p_preset->get("permissions/" + String(*aperms).to_lower());
+ if (enabled) {
+ r_permissions.push_back("android.permission." + String(*aperms));
+ }
+ aperms++;
+ }
+ PackedStringArray user_perms = p_preset->get("permissions/custom_permissions");
+ for (int i = 0; i < user_perms.size(); i++) {
+ String user_perm = user_perms[i].strip_edges();
+ if (!user_perm.is_empty()) {
+ r_permissions.push_back(user_perm);
+ }
+ }
+ if (p_give_internet) {
+ if (r_permissions.find("android.permission.INTERNET") == -1) {
+ r_permissions.push_back("android.permission.INTERNET");
+ }
+ }
+
+ int xr_mode_index = p_preset->get("xr_features/xr_mode");
+ if (xr_mode_index == 1 /* XRMode.OVR */) {
+ int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
+ if (hand_tracking_index > 0) {
+ if (r_permissions.find("com.oculus.permission.HAND_TRACKING") == -1) {
+ r_permissions.push_back("com.oculus.permission.HAND_TRACKING");
+ }
+ }
+ }
+}
+
+void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) {
+ print_verbose("Building temporary manifest..");
+ String manifest_text =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " xmlns:tools=\"http://schemas.android.com/tools\">\n";
+
+ manifest_text += _get_screen_sizes_tag(p_preset);
+ manifest_text += _get_gles_tag();
+
+ Vector<String> perms;
+ _get_permissions(p_preset, p_give_internet, perms);
+ for (int i = 0; i < perms.size(); i++) {
+ String permission = perms.get(i);
+ if (permission == "android.permission.WRITE_EXTERNAL_STORAGE" || permission == "android.permission.READ_EXTERNAL_STORAGE") {
+ manifest_text += vformat(" <uses-permission android:name=\"%s\" android:maxSdkVersion=\"29\" />\n", permission);
+ } else {
+ manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", permission);
+ }
+ }
+
+ manifest_text += _get_xr_features_tag(p_preset);
+ manifest_text += _get_instrumentation_tag(p_preset);
+ manifest_text += _get_application_tag(p_preset, _has_storage_permission(perms));
+ manifest_text += "</manifest>\n";
+ String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"));
+
+ print_verbose("Storing manifest into " + manifest_path + ": " + "\n" + manifest_text);
+ store_string_at_path(manifest_path, manifest_text);
+}
+
+void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) {
+ // Leaving the unused types commented because looking these constants up
+ // again later would be annoying
+ // const int CHUNK_AXML_FILE = 0x00080003;
+ // const int CHUNK_RESOURCEIDS = 0x00080180;
+ const int CHUNK_STRINGS = 0x001C0001;
+ // const int CHUNK_XML_END_NAMESPACE = 0x00100101;
+ const int CHUNK_XML_END_TAG = 0x00100103;
+ // const int CHUNK_XML_START_NAMESPACE = 0x00100100;
+ const int CHUNK_XML_START_TAG = 0x00100102;
+ // const int CHUNK_XML_TEXT = 0x00100104;
+ const int UTF8_FLAG = 0x00000100;
+
+ Vector<String> string_table;
+
+ uint32_t ofs = 8;
+
+ uint32_t string_count = 0;
+ //uint32_t styles_count = 0;
+ uint32_t string_flags = 0;
+ uint32_t string_data_offset = 0;
+
+ //uint32_t styles_offset = 0;
+ uint32_t string_table_begins = 0;
+ uint32_t string_table_ends = 0;
+ Vector<uint8_t> stable_extra;
+
+ String version_name = p_preset->get("version/name");
+ int version_code = p_preset->get("version/code");
+ String package_name = p_preset->get("package/unique_name");
+
+ const int screen_orientation =
+ _get_android_orientation_value(DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation"))));
+
+ bool screen_support_small = p_preset->get("screen/support_small");
+ bool screen_support_normal = p_preset->get("screen/support_normal");
+ bool screen_support_large = p_preset->get("screen/support_large");
+ bool screen_support_xlarge = p_preset->get("screen/support_xlarge");
+
+ int xr_mode_index = p_preset->get("xr_features/xr_mode");
+
+ bool backup_allowed = p_preset->get("user_data_backup/allow");
+ bool classify_as_game = p_preset->get("package/classify_as_game");
+ bool retain_data_on_uninstall = p_preset->get("package/retain_data_on_uninstall");
+
+ Vector<String> perms;
+ // Write permissions into the perms variable.
+ _get_permissions(p_preset, p_give_internet, perms);
+ bool has_storage_permission = _has_storage_permission(perms);
+
+ while (ofs < (uint32_t)p_manifest.size()) {
+ uint32_t chunk = decode_uint32(&p_manifest[ofs]);
+ uint32_t size = decode_uint32(&p_manifest[ofs + 4]);
+
+ switch (chunk) {
+ case CHUNK_STRINGS: {
+ int iofs = ofs + 8;
+
+ string_count = decode_uint32(&p_manifest[iofs]);
+ // iofs + 4 is `styles_count`.
+ string_flags = decode_uint32(&p_manifest[iofs + 8]);
+ string_data_offset = decode_uint32(&p_manifest[iofs + 12]);
+ // iofs + 16 is `styles_offset`.
+
+ uint32_t st_offset = iofs + 20;
+ string_table.resize(string_count);
+ uint32_t string_end = 0;
+
+ string_table_begins = st_offset;
+
+ for (uint32_t i = 0; i < string_count; i++) {
+ uint32_t string_at = decode_uint32(&p_manifest[st_offset + i * 4]);
+ string_at += st_offset + string_count * 4;
+
+ ERR_FAIL_COND_MSG(string_flags & UTF8_FLAG, "Unimplemented, can't read UTF-8 string table.");
+
+ if (string_flags & UTF8_FLAG) {
+ } else {
+ uint32_t len = decode_uint16(&p_manifest[string_at]);
+ Vector<char32_t> ucstring;
+ ucstring.resize(len + 1);
+ for (uint32_t j = 0; j < len; j++) {
+ uint16_t c = decode_uint16(&p_manifest[string_at + 2 + 2 * j]);
+ ucstring.write[j] = c;
+ }
+ string_end = MAX(string_at + 2 + 2 * len, string_end);
+ ucstring.write[len] = 0;
+ string_table.write[i] = ucstring.ptr();
+ }
+ }
+
+ for (uint32_t i = string_end; i < (ofs + size); i++) {
+ stable_extra.push_back(p_manifest[i]);
+ }
+
+ string_table_ends = ofs + size;
+
+ } break;
+ case CHUNK_XML_START_TAG: {
+ int iofs = ofs + 8;
+ uint32_t name = decode_uint32(&p_manifest[iofs + 12]);
+
+ String tname = string_table[name];
+ uint32_t attrcount = decode_uint32(&p_manifest[iofs + 20]);
+ iofs += 28;
+
+ for (uint32_t i = 0; i < attrcount; i++) {
+ uint32_t attr_nspace = decode_uint32(&p_manifest[iofs]);
+ uint32_t attr_name = decode_uint32(&p_manifest[iofs + 4]);
+ uint32_t attr_value = decode_uint32(&p_manifest[iofs + 8]);
+ uint32_t attr_resid = decode_uint32(&p_manifest[iofs + 16]);
+
+ const String value = (attr_value != 0xFFFFFFFF) ? string_table[attr_value] : "Res #" + itos(attr_resid);
+ String attrname = string_table[attr_name];
+ const String nspace = (attr_nspace != 0xFFFFFFFF) ? string_table[attr_nspace] : "";
+
+ //replace project information
+ if (tname == "manifest" && attrname == "package") {
+ string_table.write[attr_value] = get_package_name(package_name);
+ }
+
+ if (tname == "manifest" && attrname == "versionCode") {
+ encode_uint32(version_code, &p_manifest.write[iofs + 16]);
+ }
+
+ if (tname == "manifest" && attrname == "versionName") {
+ if (attr_value == 0xFFFFFFFF) {
+ WARN_PRINT("Version name in a resource, should be plain text");
+ } else {
+ string_table.write[attr_value] = version_name;
+ }
+ }
+
+ if (tname == "application" && attrname == "requestLegacyExternalStorage") {
+ encode_uint32(has_storage_permission ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
+ }
+
+ if (tname == "application" && attrname == "allowBackup") {
+ encode_uint32(backup_allowed, &p_manifest.write[iofs + 16]);
+ }
+
+ if (tname == "application" && attrname == "isGame") {
+ encode_uint32(classify_as_game, &p_manifest.write[iofs + 16]);
+ }
+
+ if (tname == "application" && attrname == "hasFragileUserData") {
+ encode_uint32(retain_data_on_uninstall, &p_manifest.write[iofs + 16]);
+ }
+
+ if (tname == "instrumentation" && attrname == "targetPackage") {
+ string_table.write[attr_value] = get_package_name(package_name);
+ }
+
+ if (tname == "activity" && attrname == "screenOrientation") {
+ encode_uint32(screen_orientation, &p_manifest.write[iofs + 16]);
+ }
+
+ if (tname == "supports-screens") {
+ if (attrname == "smallScreens") {
+ encode_uint32(screen_support_small ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
+
+ } else if (attrname == "normalScreens") {
+ encode_uint32(screen_support_normal ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
+
+ } else if (attrname == "largeScreens") {
+ encode_uint32(screen_support_large ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
+
+ } else if (attrname == "xlargeScreens") {
+ encode_uint32(screen_support_xlarge ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
+ }
+ }
+
+ iofs += 20;
+ }
+
+ } break;
+ case CHUNK_XML_END_TAG: {
+ int iofs = ofs + 8;
+ uint32_t name = decode_uint32(&p_manifest[iofs + 12]);
+ String tname = string_table[name];
+
+ if (tname == "uses-feature") {
+ Vector<String> feature_names;
+ Vector<bool> feature_required_list;
+ Vector<int> feature_versions;
+
+ if (xr_mode_index == 1 /* XRMode.OVR */) {
+ // Set degrees of freedom
+ feature_names.push_back("android.hardware.vr.headtracking");
+ feature_required_list.push_back(true);
+ feature_versions.push_back(1);
+
+ // Check for hand tracking
+ int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
+ if (hand_tracking_index > 0) {
+ feature_names.push_back("oculus.software.handtracking");
+ feature_required_list.push_back(hand_tracking_index == 2);
+ feature_versions.push_back(-1); // no version attribute should be added.
+ }
+ }
+
+ if (feature_names.size() > 0) {
+ ofs += 24; // skip over end tag
+
+ // save manifest ending so we can restore it
+ Vector<uint8_t> manifest_end;
+ uint32_t manifest_cur_size = p_manifest.size();
+
+ manifest_end.resize(p_manifest.size() - ofs);
+ memcpy(manifest_end.ptrw(), &p_manifest[ofs], manifest_end.size());
+
+ int32_t attr_name_string = string_table.find("name");
+ ERR_FAIL_COND_MSG(attr_name_string == -1, "Template does not have 'name' attribute.");
+
+ int32_t ns_android_string = string_table.find("http://schemas.android.com/apk/res/android");
+ if (ns_android_string == -1) {
+ string_table.push_back("http://schemas.android.com/apk/res/android");
+ ns_android_string = string_table.size() - 1;
+ }
+
+ int32_t attr_uses_feature_string = string_table.find("uses-feature");
+ if (attr_uses_feature_string == -1) {
+ string_table.push_back("uses-feature");
+ attr_uses_feature_string = string_table.size() - 1;
+ }
+
+ int32_t attr_required_string = string_table.find("required");
+ if (attr_required_string == -1) {
+ string_table.push_back("required");
+ attr_required_string = string_table.size() - 1;
+ }
+
+ for (int i = 0; i < feature_names.size(); i++) {
+ String feature_name = feature_names[i];
+ bool feature_required = feature_required_list[i];
+ int feature_version = feature_versions[i];
+ bool has_version_attribute = feature_version != -1;
+
+ print_line("Adding feature " + feature_name);
+
+ int32_t feature_string = string_table.find(feature_name);
+ if (feature_string == -1) {
+ string_table.push_back(feature_name);
+ feature_string = string_table.size() - 1;
+ }
+
+ String required_value_string = feature_required ? "true" : "false";
+ int32_t required_value = string_table.find(required_value_string);
+ if (required_value == -1) {
+ string_table.push_back(required_value_string);
+ required_value = string_table.size() - 1;
+ }
+
+ int32_t attr_version_string = -1;
+ int32_t version_value = -1;
+ int tag_size;
+ int attr_count;
+ if (has_version_attribute) {
+ attr_version_string = string_table.find("version");
+ if (attr_version_string == -1) {
+ string_table.push_back("version");
+ attr_version_string = string_table.size() - 1;
+ }
+
+ version_value = string_table.find(itos(feature_version));
+ if (version_value == -1) {
+ string_table.push_back(itos(feature_version));
+ version_value = string_table.size() - 1;
+ }
+
+ tag_size = 96; // node and three attrs + end node
+ attr_count = 3;
+ } else {
+ tag_size = 76; // node and two attrs + end node
+ attr_count = 2;
+ }
+ manifest_cur_size += tag_size + 24;
+ p_manifest.resize(manifest_cur_size);
+
+ // start tag
+ encode_uint16(0x102, &p_manifest.write[ofs]); // type
+ encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
+ encode_uint32(tag_size, &p_manifest.write[ofs + 4]); // size
+ encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
+ encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
+ encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
+ encode_uint32(attr_uses_feature_string, &p_manifest.write[ofs + 20]); // name
+ encode_uint16(20, &p_manifest.write[ofs + 24]); // attr_start
+ encode_uint16(20, &p_manifest.write[ofs + 26]); // attr_size
+ encode_uint16(attr_count, &p_manifest.write[ofs + 28]); // num_attrs
+ encode_uint16(0, &p_manifest.write[ofs + 30]); // id_index
+ encode_uint16(0, &p_manifest.write[ofs + 32]); // class_index
+ encode_uint16(0, &p_manifest.write[ofs + 34]); // style_index
+
+ // android:name attribute
+ encode_uint32(ns_android_string, &p_manifest.write[ofs + 36]); // ns
+ encode_uint32(attr_name_string, &p_manifest.write[ofs + 40]); // 'name'
+ encode_uint32(feature_string, &p_manifest.write[ofs + 44]); // raw_value
+ encode_uint16(8, &p_manifest.write[ofs + 48]); // typedvalue_size
+ p_manifest.write[ofs + 50] = 0; // typedvalue_always0
+ p_manifest.write[ofs + 51] = 0x03; // typedvalue_type (string)
+ encode_uint32(feature_string, &p_manifest.write[ofs + 52]); // typedvalue reference
+
+ // android:required attribute
+ encode_uint32(ns_android_string, &p_manifest.write[ofs + 56]); // ns
+ encode_uint32(attr_required_string, &p_manifest.write[ofs + 60]); // 'name'
+ encode_uint32(required_value, &p_manifest.write[ofs + 64]); // raw_value
+ encode_uint16(8, &p_manifest.write[ofs + 68]); // typedvalue_size
+ p_manifest.write[ofs + 70] = 0; // typedvalue_always0
+ p_manifest.write[ofs + 71] = 0x03; // typedvalue_type (string)
+ encode_uint32(required_value, &p_manifest.write[ofs + 72]); // typedvalue reference
+
+ ofs += 76;
+
+ if (has_version_attribute) {
+ // android:version attribute
+ encode_uint32(ns_android_string, &p_manifest.write[ofs]); // ns
+ encode_uint32(attr_version_string, &p_manifest.write[ofs + 4]); // 'name'
+ encode_uint32(version_value, &p_manifest.write[ofs + 8]); // raw_value
+ encode_uint16(8, &p_manifest.write[ofs + 12]); // typedvalue_size
+ p_manifest.write[ofs + 14] = 0; // typedvalue_always0
+ p_manifest.write[ofs + 15] = 0x03; // typedvalue_type (string)
+ encode_uint32(version_value, &p_manifest.write[ofs + 16]); // typedvalue reference
+
+ ofs += 20;
+ }
+
+ // end tag
+ encode_uint16(0x103, &p_manifest.write[ofs]); // type
+ encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
+ encode_uint32(24, &p_manifest.write[ofs + 4]); // size
+ encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
+ encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
+ encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
+ encode_uint32(attr_uses_feature_string, &p_manifest.write[ofs + 20]); // name
+
+ ofs += 24;
+ }
+ memcpy(&p_manifest.write[ofs], manifest_end.ptr(), manifest_end.size());
+ ofs -= 24; // go back over back end
+ }
+ }
+ if (tname == "manifest") {
+ // save manifest ending so we can restore it
+ Vector<uint8_t> manifest_end;
+ uint32_t manifest_cur_size = p_manifest.size();
+
+ manifest_end.resize(p_manifest.size() - ofs);
+ memcpy(manifest_end.ptrw(), &p_manifest[ofs], manifest_end.size());
+
+ int32_t attr_name_string = string_table.find("name");
+ ERR_FAIL_COND_MSG(attr_name_string == -1, "Template does not have 'name' attribute.");
+
+ int32_t ns_android_string = string_table.find("android");
+ ERR_FAIL_COND_MSG(ns_android_string == -1, "Template does not have 'android' namespace.");
+
+ int32_t attr_uses_permission_string = string_table.find("uses-permission");
+ if (attr_uses_permission_string == -1) {
+ string_table.push_back("uses-permission");
+ attr_uses_permission_string = string_table.size() - 1;
+ }
+
+ for (int i = 0; i < perms.size(); ++i) {
+ print_line("Adding permission " + perms[i]);
+
+ manifest_cur_size += 56 + 24; // node + end node
+ p_manifest.resize(manifest_cur_size);
+
+ // Add permission to the string pool
+ int32_t perm_string = string_table.find(perms[i]);
+ if (perm_string == -1) {
+ string_table.push_back(perms[i]);
+ perm_string = string_table.size() - 1;
+ }
+
+ // start tag
+ encode_uint16(0x102, &p_manifest.write[ofs]); // type
+ encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
+ encode_uint32(56, &p_manifest.write[ofs + 4]); // size
+ encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
+ encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
+ encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
+ encode_uint32(attr_uses_permission_string, &p_manifest.write[ofs + 20]); // name
+ encode_uint16(20, &p_manifest.write[ofs + 24]); // attr_start
+ encode_uint16(20, &p_manifest.write[ofs + 26]); // attr_size
+ encode_uint16(1, &p_manifest.write[ofs + 28]); // num_attrs
+ encode_uint16(0, &p_manifest.write[ofs + 30]); // id_index
+ encode_uint16(0, &p_manifest.write[ofs + 32]); // class_index
+ encode_uint16(0, &p_manifest.write[ofs + 34]); // style_index
+
+ // attribute
+ encode_uint32(ns_android_string, &p_manifest.write[ofs + 36]); // ns
+ encode_uint32(attr_name_string, &p_manifest.write[ofs + 40]); // 'name'
+ encode_uint32(perm_string, &p_manifest.write[ofs + 44]); // raw_value
+ encode_uint16(8, &p_manifest.write[ofs + 48]); // typedvalue_size
+ p_manifest.write[ofs + 50] = 0; // typedvalue_always0
+ p_manifest.write[ofs + 51] = 0x03; // typedvalue_type (string)
+ encode_uint32(perm_string, &p_manifest.write[ofs + 52]); // typedvalue reference
+
+ ofs += 56;
+
+ // end tag
+ encode_uint16(0x103, &p_manifest.write[ofs]); // type
+ encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
+ encode_uint32(24, &p_manifest.write[ofs + 4]); // size
+ encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
+ encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
+ encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
+ encode_uint32(attr_uses_permission_string, &p_manifest.write[ofs + 20]); // name
+
+ ofs += 24;
+ }
+
+ // copy footer back in
+ memcpy(&p_manifest.write[ofs], manifest_end.ptr(), manifest_end.size());
+ }
+ } break;
+ }
+
+ ofs += size;
+ }
+
+ //create new andriodmanifest binary
+
+ Vector<uint8_t> ret;
+ ret.resize(string_table_begins + string_table.size() * 4);
+
+ for (uint32_t i = 0; i < string_table_begins; i++) {
+ ret.write[i] = p_manifest[i];
+ }
+
+ ofs = 0;
+ for (int i = 0; i < string_table.size(); i++) {
+ encode_uint32(ofs, &ret.write[string_table_begins + i * 4]);
+ ofs += string_table[i].length() * 2 + 2 + 2;
+ }
+
+ ret.resize(ret.size() + ofs);
+ string_data_offset = ret.size() - ofs;
+ uint8_t *chars = &ret.write[string_data_offset];
+ for (int i = 0; i < string_table.size(); i++) {
+ String s = string_table[i];
+ encode_uint16(s.length(), chars);
+ chars += 2;
+ for (int j = 0; j < s.length(); j++) {
+ encode_uint16(s[j], chars);
+ chars += 2;
+ }
+ encode_uint16(0, chars);
+ chars += 2;
+ }
+
+ for (int i = 0; i < stable_extra.size(); i++) {
+ ret.push_back(stable_extra[i]);
+ }
+
+ //pad
+ while (ret.size() % 4) {
+ ret.push_back(0);
+ }
+
+ uint32_t new_stable_end = ret.size();
+
+ uint32_t extra = (p_manifest.size() - string_table_ends);
+ ret.resize(new_stable_end + extra);
+ for (uint32_t i = 0; i < extra; i++) {
+ ret.write[new_stable_end + i] = p_manifest[string_table_ends + i];
+ }
+
+ while (ret.size() % 4) {
+ ret.push_back(0);
+ }
+ encode_uint32(ret.size(), &ret.write[4]); //update new file size
+
+ encode_uint32(new_stable_end - 8, &ret.write[12]); //update new string table size
+ encode_uint32(string_table.size(), &ret.write[16]); //update new number of strings
+ encode_uint32(string_data_offset - 8, &ret.write[28]); //update new string data offset
+
+ p_manifest = ret;
+}
+
+String EditorExportPlatformAndroid::_parse_string(const uint8_t *p_bytes, bool p_utf8) {
+ uint32_t offset = 0;
+ uint32_t len = 0;
+
+ if (p_utf8) {
+ uint8_t byte = p_bytes[offset];
+ if (byte & 0x80) {
+ offset += 2;
+ } else {
+ offset += 1;
+ }
+ byte = p_bytes[offset];
+ offset++;
+ if (byte & 0x80) {
+ len = byte & 0x7F;
+ len = (len << 8) + p_bytes[offset];
+ offset++;
+ } else {
+ len = byte;
+ }
+ } else {
+ len = decode_uint16(&p_bytes[offset]);
+ offset += 2;
+ if (len & 0x8000) {
+ len &= 0x7FFF;
+ len = (len << 16) + decode_uint16(&p_bytes[offset]);
+ offset += 2;
+ }
+ }
+
+ if (p_utf8) {
+ Vector<uint8_t> str8;
+ str8.resize(len + 1);
+ for (uint32_t i = 0; i < len; i++) {
+ str8.write[i] = p_bytes[offset + i];
+ }
+ str8.write[len] = 0;
+ String str;
+ str.parse_utf8((const char *)str8.ptr());
+ return str;
+ } else {
+ String str;
+ for (uint32_t i = 0; i < len; i++) {
+ char32_t c = decode_uint16(&p_bytes[offset + i * 2]);
+ if (c == 0) {
+ break;
+ }
+ str += String::chr(c);
+ }
+ return str;
+ }
+}
+
+void EditorExportPlatformAndroid::_fix_resources(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &r_manifest) {
+ const int UTF8_FLAG = 0x00000100;
+
+ uint32_t string_block_len = decode_uint32(&r_manifest[16]);
+ uint32_t string_count = decode_uint32(&r_manifest[20]);
+ uint32_t string_flags = decode_uint32(&r_manifest[28]);
+ const uint32_t string_table_begins = 40;
+
+ Vector<String> string_table;
+
+ String package_name = p_preset->get("package/name");
+
+ for (uint32_t i = 0; i < string_count; i++) {
+ uint32_t offset = decode_uint32(&r_manifest[string_table_begins + i * 4]);
+ offset += string_table_begins + string_count * 4;
+
+ String str = _parse_string(&r_manifest[offset], string_flags & UTF8_FLAG);
+
+ if (str.begins_with("godot-project-name")) {
+ if (str == "godot-project-name") {
+ //project name
+ str = get_project_name(package_name);
+
+ } else {
+ String lang = str.substr(str.rfind("-") + 1, str.length()).replace("-", "_");
+ String prop = "application/config/name_" + lang;
+ if (ProjectSettings::get_singleton()->has_setting(prop)) {
+ str = ProjectSettings::get_singleton()->get(prop);
+ } else {
+ str = get_project_name(package_name);
+ }
+ }
+ }
+
+ string_table.push_back(str);
+ }
+
+ //write a new string table, but use 16 bits
+ Vector<uint8_t> ret;
+ ret.resize(string_table_begins + string_table.size() * 4);
+
+ for (uint32_t i = 0; i < string_table_begins; i++) {
+ ret.write[i] = r_manifest[i];
+ }
+
+ int ofs = 0;
+ for (int i = 0; i < string_table.size(); i++) {
+ encode_uint32(ofs, &ret.write[string_table_begins + i * 4]);
+ ofs += string_table[i].length() * 2 + 2 + 2;
+ }
+
+ ret.resize(ret.size() + ofs);
+ uint8_t *chars = &ret.write[ret.size() - ofs];
+ for (int i = 0; i < string_table.size(); i++) {
+ String s = string_table[i];
+ encode_uint16(s.length(), chars);
+ chars += 2;
+ for (int j = 0; j < s.length(); j++) {
+ encode_uint16(s[j], chars);
+ chars += 2;
+ }
+ encode_uint16(0, chars);
+ chars += 2;
+ }
+
+ //pad
+ while (ret.size() % 4) {
+ ret.push_back(0);
+ }
+
+ //change flags to not use utf8
+ encode_uint32(string_flags & ~0x100, &ret.write[28]);
+ //change length
+ encode_uint32(ret.size() - 12, &ret.write[16]);
+ //append the rest...
+ int rest_from = 12 + string_block_len;
+ int rest_to = ret.size();
+ int rest_len = (r_manifest.size() - rest_from);
+ ret.resize(ret.size() + (r_manifest.size() - rest_from));
+ for (int i = 0; i < rest_len; i++) {
+ ret.write[rest_to + i] = r_manifest[rest_from + i];
+ }
+ //finally update the size
+ encode_uint32(ret.size(), &ret.write[4]);
+
+ r_manifest = ret;
+ //printf("end\n");
+}
+
+void EditorExportPlatformAndroid::_load_image_data(const Ref<Image> &p_splash_image, Vector<uint8_t> &p_data) {
+ Vector<uint8_t> png_buffer;
+ Error err = PNGDriverCommon::image_to_png(p_splash_image, png_buffer);
+ if (err == OK) {
+ p_data.resize(png_buffer.size());
+ memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size());
+ } else {
+ String err_str = String("Failed to convert splash image to png.");
+ WARN_PRINT(err_str.utf8().get_data());
+ }
+}
+
+void EditorExportPlatformAndroid::_process_launcher_icons(const String &p_file_name, const Ref<Image> &p_source_image, int dimension, Vector<uint8_t> &p_data) {
+ Ref<Image> working_image = p_source_image;
+
+ if (p_source_image->get_width() != dimension || p_source_image->get_height() != dimension) {
+ working_image = p_source_image->duplicate();
+ working_image->resize(dimension, dimension, Image::Interpolation::INTERPOLATE_LANCZOS);
+ }
+
+ Vector<uint8_t> png_buffer;
+ Error err = PNGDriverCommon::image_to_png(working_image, png_buffer);
+ if (err == OK) {
+ p_data.resize(png_buffer.size());
+ memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size());
+ } else {
+ String err_str = String("Failed to convert resized icon (") + p_file_name + ") to png.";
+ WARN_PRINT(err_str.utf8().get_data());
+ }
+}
+
+String EditorExportPlatformAndroid::load_splash_refs(Ref<Image> &splash_image, Ref<Image> &splash_bg_color_image) {
+ bool scale_splash = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize");
+ bool apply_filter = ProjectSettings::get_singleton()->get("application/boot_splash/use_filter");
+ String project_splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image");
+
+ if (!project_splash_path.is_empty()) {
+ splash_image.instantiate();
+ print_verbose("Loading splash image: " + project_splash_path);
+ const Error err = ImageLoader::load_image(project_splash_path, splash_image);
+ if (err) {
+ if (OS::get_singleton()->is_stdout_verbose()) {
+ print_error("- unable to load splash image from " + project_splash_path + " (" + itos(err) + ")");
+ }
+ splash_image.unref();
+ }
+ }
+
+ if (splash_image.is_null()) {
+ // Use the default
+ print_verbose("Using default splash image.");
+ splash_image = Ref<Image>(memnew(Image(boot_splash_png)));
+ }
+
+ if (scale_splash) {
+ Size2 screen_size = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"));
+ int width, height;
+ if (screen_size.width > screen_size.height) {
+ // scale horizontally
+ height = screen_size.height;
+ width = splash_image->get_width() * screen_size.height / splash_image->get_height();
+ } else {
+ // scale vertically
+ width = screen_size.width;
+ height = splash_image->get_height() * screen_size.width / splash_image->get_width();
+ }
+ splash_image->resize(width, height);
+ }
+
+ // Setup the splash bg color
+ bool bg_color_valid;
+ Color bg_color = ProjectSettings::get_singleton()->get("application/boot_splash/bg_color", &bg_color_valid);
+ if (!bg_color_valid) {
+ bg_color = boot_splash_bg_color;
+ }
+
+ print_verbose("Creating splash background color image.");
+ splash_bg_color_image.instantiate();
+ splash_bg_color_image->create(splash_image->get_width(), splash_image->get_height(), false, splash_image->get_format());
+ splash_bg_color_image->fill(bg_color);
+
+ String processed_splash_config_xml = vformat(SPLASH_CONFIG_XML_CONTENT, bool_to_string(apply_filter));
+ return processed_splash_config_xml;
+}
+
+void EditorExportPlatformAndroid::load_icon_refs(const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background) {
+ String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon");
+
+ icon.instantiate();
+ foreground.instantiate();
+ background.instantiate();
+
+ // Regular icon: user selection -> project icon -> default.
+ String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges();
+ print_verbose("Loading regular icon from " + path);
+ if (path.is_empty() || ImageLoader::load_image(path, icon) != OK) {
+ print_verbose("- falling back to project icon: " + project_icon_path);
+ ImageLoader::load_image(project_icon_path, icon);
+ }
+
+ // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default).
+ path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges();
+ print_verbose("Loading adaptive foreground icon from " + path);
+ if (path.is_empty() || ImageLoader::load_image(path, foreground) != OK) {
+ print_verbose("- falling back to using the regular icon");
+ foreground = icon;
+ }
+
+ // Adaptive background: user selection -> default.
+ path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges();
+ if (!path.is_empty()) {
+ print_verbose("Loading adaptive background icon from " + path);
+ ImageLoader::load_image(path, background);
+ }
+}
+
+void EditorExportPlatformAndroid::store_image(const LauncherIcon launcher_icon, const Vector<uint8_t> &data) {
+ store_image(launcher_icon.export_path, data);
+}
+
+void EditorExportPlatformAndroid::store_image(const String &export_path, const Vector<uint8_t> &data) {
+ String img_path = export_path.insert(0, "res://android/build/");
+ store_file_at_path(img_path, data);
+}
+
+void EditorExportPlatformAndroid::_copy_icons_to_gradle_project(const Ref<EditorExportPreset> &p_preset,
+ const String &processed_splash_config_xml,
+ const Ref<Image> &splash_image,
+ const Ref<Image> &splash_bg_color_image,
+ const Ref<Image> &main_image,
+ const Ref<Image> &foreground,
+ const Ref<Image> &background) {
+ // Store the splash configuration
+ if (!processed_splash_config_xml.is_empty()) {
+ print_verbose("Storing processed splash configuration: " + String("\n") + processed_splash_config_xml);
+ store_string_at_path(SPLASH_CONFIG_PATH, processed_splash_config_xml);
+ }
+
+ // Store the splash image
+ if (splash_image.is_valid() && !splash_image->is_empty()) {
+ print_verbose("Storing splash image in " + String(SPLASH_IMAGE_EXPORT_PATH));
+ Vector<uint8_t> data;
+ _load_image_data(splash_image, data);
+ store_image(SPLASH_IMAGE_EXPORT_PATH, data);
+ }
+
+ // Store the splash bg color image
+ if (splash_bg_color_image.is_valid() && !splash_bg_color_image->is_empty()) {
+ print_verbose("Storing splash background image in " + String(SPLASH_BG_COLOR_PATH));
+ Vector<uint8_t> data;
+ _load_image_data(splash_bg_color_image, data);
+ store_image(SPLASH_BG_COLOR_PATH, data);
+ }
+
+ // Prepare images to be resized for the icons. If some image ends up being uninitialized,
+ // the default image from the export template will be used.
+
+ for (int i = 0; i < icon_densities_count; ++i) {
+ if (main_image.is_valid() && !main_image->is_empty()) {
+ print_verbose("Processing launcher icon for dimension " + itos(launcher_icons[i].dimensions) + " into " + launcher_icons[i].export_path);
+ Vector<uint8_t> data;
+ _process_launcher_icons(launcher_icons[i].export_path, main_image, launcher_icons[i].dimensions, data);
+ store_image(launcher_icons[i], data);
+ }
+
+ if (foreground.is_valid() && !foreground->is_empty()) {
+ print_verbose("Processing launcher adaptive icon foreground for dimension " + itos(launcher_adaptive_icon_foregrounds[i].dimensions) + " into " + launcher_adaptive_icon_foregrounds[i].export_path);
+ Vector<uint8_t> data;
+ _process_launcher_icons(launcher_adaptive_icon_foregrounds[i].export_path, foreground,
+ launcher_adaptive_icon_foregrounds[i].dimensions, data);
+ store_image(launcher_adaptive_icon_foregrounds[i], data);
+ }
+
+ if (background.is_valid() && !background->is_empty()) {
+ print_verbose("Processing launcher adaptive icon background for dimension " + itos(launcher_adaptive_icon_backgrounds[i].dimensions) + " into " + launcher_adaptive_icon_backgrounds[i].export_path);
+ Vector<uint8_t> data;
+ _process_launcher_icons(launcher_adaptive_icon_backgrounds[i].export_path, background,
+ launcher_adaptive_icon_backgrounds[i].dimensions, data);
+ store_image(launcher_adaptive_icon_backgrounds[i], data);
+ }
+ }
+}
+
+Vector<String> EditorExportPlatformAndroid::get_enabled_abis(const Ref<EditorExportPreset> &p_preset) {
+ Vector<String> abis = get_abis();
+ Vector<String> enabled_abis;
+ for (int i = 0; i < abis.size(); ++i) {
+ bool is_enabled = p_preset->get("architectures/" + abis[i]);
+ if (is_enabled) {
+ enabled_abis.push_back(abis[i]);
+ }
+ }
+ return enabled_abis;
+}
+
+void EditorExportPlatformAndroid::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
+ String driver = ProjectSettings::get_singleton()->get("rendering/driver/driver_name");
+ if (driver == "opengl3") {
+ r_features->push_back("etc");
+ }
+ // FIXME: Review what texture formats are used for Vulkan.
+ if (driver == "vulkan") {
+ r_features->push_back("etc2");
+ }
+
+ Vector<String> abis = get_enabled_abis(p_preset);
+ for (int i = 0; i < abis.size(); ++i) {
+ r_features->push_back(abis[i]);
+ }
+}
+
+void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_options) {
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_template/use_custom_build"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "custom_template/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), EXPORT_FORMAT_APK));
+
+ Vector<PluginConfigAndroid> plugins_configs = get_plugins();
+ for (int i = 0; i < plugins_configs.size(); i++) {
+ print_verbose("Found Android plugin " + plugins_configs[i].name);
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + plugins_configs[i].name), false));
+ }
+ plugins_changed.clear();
+
+ const Vector<String> abis = get_abis();
+ for (int i = 0; i < abis.size(); ++i) {
+ const String abi = abis[i];
+ // All Android devices supporting Vulkan run 64-bit Android,
+ // so there is usually no point in exporting for 32-bit Android.
+ const bool is_default = abi == "arm64-v8a";
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "architectures/" + abi), is_default));
+ }
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_user"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password"), ""));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0"));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname"));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/signed"), true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/classify_as_game"), true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/retain_data_on_uninstall"), false));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_icon_option, PROPERTY_HINT_FILE, "*.png"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_foreground_option, PROPERTY_HINT_FILE, "*.png"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_background_option, PROPERTY_HINT_FILE, "*.png"), ""));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/opengl_debug"), false));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/xr_mode", PROPERTY_HINT_ENUM, "Regular,Oculus Mobile VR"), 0));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking", PROPERTY_HINT_ENUM, "None,Optional,Required"), 0));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/immersive_mode"), true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_small"), true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_normal"), true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_large"), true));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_xlarge"), true));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data_backup/allow"), false));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), ""));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "apk_expansion/enable"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "apk_expansion/SALT"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "apk_expansion/public_key", PROPERTY_HINT_MULTILINE_TEXT), ""));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "permissions/custom_permissions"), PackedStringArray()));
+
+ const char **perms = android_perms;
+ while (*perms) {
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "permissions/" + String(*perms).to_lower()), false));
+ perms++;
+ }
+}
+
+String EditorExportPlatformAndroid::get_name() const {
+ return "Android";
+}
+
+String EditorExportPlatformAndroid::get_os_name() const {
+ return "Android";
+}
+
+Ref<Texture2D> EditorExportPlatformAndroid::get_logo() const {
+ return logo;
+}
+
+bool EditorExportPlatformAndroid::should_update_export_options() {
+ bool export_options_changed = plugins_changed.is_set();
+ if (export_options_changed) {
+ // don't clear unless we're reporting true, to avoid race
+ plugins_changed.clear();
+ }
+ return export_options_changed;
+}
+
+bool EditorExportPlatformAndroid::poll_export() {
+ bool dc = devices_changed.is_set();
+ if (dc) {
+ // don't clear unless we're reporting true, to avoid race
+ devices_changed.clear();
+ }
+ return dc;
+}
+
+int EditorExportPlatformAndroid::get_options_count() const {
+ MutexLock lock(device_lock);
+ return devices.size();
+}
+
+String EditorExportPlatformAndroid::get_options_tooltip() const {
+ return TTR("Select device from the list");
+}
+
+String EditorExportPlatformAndroid::get_option_label(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, devices.size(), "");
+ MutexLock lock(device_lock);
+ return devices[p_index].name;
+}
+
+String EditorExportPlatformAndroid::get_option_tooltip(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, devices.size(), "");
+ MutexLock lock(device_lock);
+ String s = devices[p_index].description;
+ if (devices.size() == 1) {
+ // Tooltip will be:
+ // Name
+ // Description
+ s = devices[p_index].name + "\n\n" + s;
+ }
+ return s;
+}
+
+Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+ ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER);
+
+ String can_export_error;
+ bool can_export_missing_templates;
+ if (!can_export(p_preset, can_export_error, can_export_missing_templates)) {
+ EditorNode::add_io_error(can_export_error);
+ return ERR_UNCONFIGURED;
+ }
+
+ MutexLock lock(device_lock);
+
+ EditorProgress ep("run", vformat(TTR("Running on %s"), devices[p_device].name), 3);
+
+ String adb = get_adb_path();
+
+ // Export_temp APK.
+ if (ep.step(TTR("Exporting APK..."), 0)) {
+ return ERR_SKIP;
+ }
+
+ const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
+ const bool use_reverse = devices[p_device].api_level >= 21;
+
+ if (use_reverse) {
+ p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST;
+ }
+
+ String tmp_export_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpexport." + uitos(OS::get_singleton()->get_unix_time()) + ".apk");
+
+#define CLEANUP_AND_RETURN(m_err) \
+ { \
+ DirAccess::remove_file_or_error(tmp_export_path); \
+ return m_err; \
+ }
+
+ // Export to temporary APK before sending to device.
+ Error err = export_project_helper(p_preset, true, tmp_export_path, EXPORT_FORMAT_APK, true, p_debug_flags);
+
+ if (err != OK) {
+ CLEANUP_AND_RETURN(err);
+ }
+
+ List<String> args;
+ int rv;
+ String output;
+
+ bool remove_prev = EDITOR_GET("export/android/one_click_deploy_clear_previous_install");
+ String version_name = p_preset->get("version/name");
+ String package_name = p_preset->get("package/unique_name");
+
+ if (remove_prev) {
+ if (ep.step(TTR("Uninstalling..."), 1)) {
+ CLEANUP_AND_RETURN(ERR_SKIP);
+ }
+
+ print_line("Uninstalling previous version: " + devices[p_device].name);
+
+ args.push_back("-s");
+ args.push_back(devices[p_device].id);
+ args.push_back("uninstall");
+ args.push_back(get_package_name(package_name));
+
+ output.clear();
+ err = OS::get_singleton()->execute(adb, args, &output, &rv, true);
+ print_verbose(output);
+ }
+
+ print_line("Installing to device (please wait...): " + devices[p_device].name);
+ if (ep.step(TTR("Installing to device, please wait..."), 2)) {
+ CLEANUP_AND_RETURN(ERR_SKIP);
+ }
+
+ args.clear();
+ args.push_back("-s");
+ args.push_back(devices[p_device].id);
+ args.push_back("install");
+ args.push_back("-r");
+ args.push_back(tmp_export_path);
+
+ output.clear();
+ err = OS::get_singleton()->execute(adb, args, &output, &rv, true);
+ print_verbose(output);
+ if (err || rv != 0) {
+ EditorNode::add_io_error(vformat(TTR("Could not install to device: %s"), output));
+ CLEANUP_AND_RETURN(ERR_CANT_CREATE);
+ }
+
+ if (use_remote) {
+ if (use_reverse) {
+ static const char *const msg = "--- Device API >= 21; debugging over USB ---";
+ EditorNode::get_singleton()->get_log()->add_message(msg, EditorLog::MSG_TYPE_EDITOR);
+ print_line(String(msg).to_upper());
+
+ args.clear();
+ args.push_back("-s");
+ args.push_back(devices[p_device].id);
+ args.push_back("reverse");
+ args.push_back("--remove-all");
+ output.clear();
+ OS::get_singleton()->execute(adb, args, &output, &rv, true);
+ print_verbose(output);
+
+ if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) {
+ int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
+ args.clear();
+ args.push_back("-s");
+ args.push_back(devices[p_device].id);
+ args.push_back("reverse");
+ args.push_back("tcp:" + itos(dbg_port));
+ args.push_back("tcp:" + itos(dbg_port));
+
+ output.clear();
+ OS::get_singleton()->execute(adb, args, &output, &rv, true);
+ print_verbose(output);
+ print_line("Reverse result: " + itos(rv));
+ }
+
+ if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ int fs_port = EditorSettings::get_singleton()->get("filesystem/file_server/port");
+
+ args.clear();
+ args.push_back("-s");
+ args.push_back(devices[p_device].id);
+ args.push_back("reverse");
+ args.push_back("tcp:" + itos(fs_port));
+ args.push_back("tcp:" + itos(fs_port));
+
+ output.clear();
+ err = OS::get_singleton()->execute(adb, args, &output, &rv, true);
+ print_verbose(output);
+ print_line("Reverse result2: " + itos(rv));
+ }
+ } else {
+ static const char *const msg = "--- Device API < 21; debugging over Wi-Fi ---";
+ EditorNode::get_singleton()->get_log()->add_message(msg, EditorLog::MSG_TYPE_EDITOR);
+ print_line(String(msg).to_upper());
+ }
+ }
+
+ if (ep.step(TTR("Running on device..."), 3)) {
+ CLEANUP_AND_RETURN(ERR_SKIP);
+ }
+ args.clear();
+ args.push_back("-s");
+ args.push_back(devices[p_device].id);
+ args.push_back("shell");
+ args.push_back("am");
+ args.push_back("start");
+ if ((bool)EditorSettings::get_singleton()->get("export/android/force_system_user") && devices[p_device].api_level >= 17) { // Multi-user introduced in Android 17
+ args.push_back("--user");
+ args.push_back("0");
+ }
+ args.push_back("-a");
+ args.push_back("android.intent.action.MAIN");
+ args.push_back("-n");
+ args.push_back(get_package_name(package_name) + "/com.godot.game.GodotApp");
+
+ output.clear();
+ err = OS::get_singleton()->execute(adb, args, &output, &rv, true);
+ print_verbose(output);
+ if (err || rv != 0) {
+ EditorNode::add_io_error(TTR("Could not execute on device."));
+ CLEANUP_AND_RETURN(ERR_CANT_CREATE);
+ }
+
+ CLEANUP_AND_RETURN(OK);
+#undef CLEANUP_AND_RETURN
+}
+
+Ref<Texture2D> EditorExportPlatformAndroid::get_run_icon() const {
+ return run_icon;
+}
+
+String EditorExportPlatformAndroid::get_adb_path() {
+ String exe_ext = "";
+ if (OS::get_singleton()->get_name() == "Windows") {
+ exe_ext = ".exe";
+ }
+ String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path");
+ return sdk_path.plus_file("platform-tools/adb" + exe_ext);
+}
+
+String EditorExportPlatformAndroid::get_apksigner_path() {
+ String exe_ext = "";
+ if (OS::get_singleton()->get_name() == "Windows") {
+ exe_ext = ".bat";
+ }
+ String apksigner_command_name = "apksigner" + exe_ext;
+ String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path");
+ String apksigner_path = "";
+
+ Error errn;
+ String build_tools_dir = sdk_path.plus_file("build-tools");
+ DirAccessRef da = DirAccess::open(build_tools_dir, &errn);
+ if (errn != OK) {
+ print_error("Unable to open Android 'build-tools' directory.");
+ return apksigner_path;
+ }
+
+ // There are additional versions directories we need to go through.
+ da->list_dir_begin();
+ String sub_dir = da->get_next();
+ while (!sub_dir.is_empty()) {
+ if (!sub_dir.begins_with(".") && da->current_is_dir()) {
+ // Check if the tool is here.
+ String tool_path = build_tools_dir.plus_file(sub_dir).plus_file(apksigner_command_name);
+ if (FileAccess::exists(tool_path)) {
+ apksigner_path = tool_path;
+ break;
+ }
+ }
+ sub_dir = da->get_next();
+ }
+ da->list_dir_end();
+
+ if (apksigner_path.is_empty()) {
+ EditorNode::get_singleton()->show_warning(TTR("Unable to find the 'apksigner' tool."));
+ }
+
+ return apksigner_path;
+}
+
+bool EditorExportPlatformAndroid::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+ String err;
+ bool valid = false;
+
+ // Look for export templates (first official, and if defined custom templates).
+
+ if (!bool(p_preset->get("custom_template/use_custom_build"))) {
+ String template_err;
+ bool dvalid = false;
+ bool rvalid = false;
+ bool has_export_templates = false;
+
+ if (p_preset->get("custom_template/debug") != "") {
+ dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
+ if (!dvalid) {
+ template_err += TTR("Custom debug template not found.") + "\n";
+ }
+ } else {
+ has_export_templates |= exists_export_template("android_debug.apk", &template_err);
+ }
+
+ if (p_preset->get("custom_template/release") != "") {
+ rvalid = FileAccess::exists(p_preset->get("custom_template/release"));
+ if (!rvalid) {
+ template_err += TTR("Custom release template not found.") + "\n";
+ }
+ } else {
+ has_export_templates |= exists_export_template("android_release.apk", &template_err);
+ }
+
+ r_missing_templates = !has_export_templates;
+ valid = dvalid || rvalid || has_export_templates;
+ if (!valid) {
+ err += template_err;
+ }
+ } else {
+ bool installed_android_build_template = FileAccess::exists("res://android/build/build.gradle");
+ if (!installed_android_build_template) {
+ r_missing_templates = !exists_export_template("android_source.zip", &err);
+ err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n";
+ } else {
+ r_missing_templates = false;
+ }
+
+ valid = installed_android_build_template && !r_missing_templates;
+ }
+
+ // Validate the rest of the configuration.
+
+ String dk = p_preset->get("keystore/debug");
+ String dk_user = p_preset->get("keystore/debug_user");
+ String dk_password = p_preset->get("keystore/debug_password");
+
+ if ((dk.is_empty() || dk_user.is_empty() || dk_password.is_empty()) && (!dk.is_empty() || !dk_user.is_empty() || !dk_password.is_empty())) {
+ valid = false;
+ err += TTR("Either Debug Keystore, Debug User AND Debug Password settings must be configured OR none of them.") + "\n";
+ }
+
+ if (!FileAccess::exists(dk)) {
+ dk = EditorSettings::get_singleton()->get("export/android/debug_keystore");
+ if (!FileAccess::exists(dk)) {
+ valid = false;
+ err += TTR("Debug keystore not configured in the Editor Settings nor in the preset.") + "\n";
+ }
+ }
+
+ String rk = p_preset->get("keystore/release");
+ String rk_user = p_preset->get("keystore/release_user");
+ String rk_password = p_preset->get("keystore/release_password");
+
+ if ((rk.is_empty() || rk_user.is_empty() || rk_password.is_empty()) && (!rk.is_empty() || !rk_user.is_empty() || !rk_password.is_empty())) {
+ valid = false;
+ err += TTR("Either Release Keystore, Release User AND Release Password settings must be configured OR none of them.") + "\n";
+ }
+
+ if (!rk.is_empty() && !FileAccess::exists(rk)) {
+ valid = false;
+ err += TTR("Release keystore incorrectly configured in the export preset.") + "\n";
+ }
+
+ String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path");
+ if (sdk_path == "") {
+ err += TTR("A valid Android SDK path is required in Editor Settings.") + "\n";
+ valid = false;
+ } else {
+ Error errn;
+ // Check for the platform-tools directory.
+ DirAccessRef da = DirAccess::open(sdk_path.plus_file("platform-tools"), &errn);
+ if (errn != OK) {
+ err += TTR("Invalid Android SDK path in Editor Settings.");
+ err += TTR("Missing 'platform-tools' directory!");
+ err += "\n";
+ valid = false;
+ }
+
+ // Validate that adb is available
+ String adb_path = get_adb_path();
+ if (!FileAccess::exists(adb_path)) {
+ err += TTR("Unable to find Android SDK platform-tools' adb command.");
+ err += TTR("Please check in the Android SDK directory specified in Editor Settings.");
+ err += "\n";
+ valid = false;
+ }
+
+ // Check for the build-tools directory.
+ DirAccessRef build_tools_da = DirAccess::open(sdk_path.plus_file("build-tools"), &errn);
+ if (errn != OK) {
+ err += TTR("Invalid Android SDK path in Editor Settings.");
+ err += TTR("Missing 'build-tools' directory!");
+ err += "\n";
+ valid = false;
+ }
+
+ // Validate that apksigner is available
+ String apksigner_path = get_apksigner_path();
+ if (!FileAccess::exists(apksigner_path)) {
+ err += TTR("Unable to find Android SDK build-tools' apksigner command.");
+ err += TTR("Please check in the Android SDK directory specified in Editor Settings.");
+ err += "\n";
+ valid = false;
+ }
+ }
+
+ bool apk_expansion = p_preset->get("apk_expansion/enable");
+
+ if (apk_expansion) {
+ String apk_expansion_pkey = p_preset->get("apk_expansion/public_key");
+
+ if (apk_expansion_pkey == "") {
+ valid = false;
+
+ err += TTR("Invalid public key for APK expansion.") + "\n";
+ }
+ }
+
+ String pn = p_preset->get("package/unique_name");
+ String pn_err;
+
+ if (!is_package_name_valid(get_package_name(pn), &pn_err)) {
+ valid = false;
+ err += TTR("Invalid package name:") + " " + pn_err + "\n";
+ }
+
+ String etc_error = test_etc2();
+ if (etc_error != String()) {
+ valid = false;
+ err += etc_error;
+ }
+
+ // Ensure that `Use Custom Build` is enabled if a plugin is selected.
+ String enabled_plugins_names = PluginConfigAndroid::get_plugins_names(get_enabled_plugins(p_preset));
+ bool custom_build_enabled = p_preset->get("custom_template/use_custom_build");
+ if (!enabled_plugins_names.is_empty() && !custom_build_enabled) {
+ valid = false;
+ err += TTR("\"Use Custom Build\" must be enabled to use the plugins.");
+ err += "\n";
+ }
+
+ // Validate the Xr features are properly populated
+ int xr_mode_index = p_preset->get("xr_features/xr_mode");
+ int hand_tracking = p_preset->get("xr_features/hand_tracking");
+ if (xr_mode_index != /* XRMode.OVR*/ 1) {
+ if (hand_tracking > 0) {
+ valid = false;
+ err += TTR("\"Hand Tracking\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\".");
+ err += "\n";
+ }
+ }
+
+ if (int(p_preset->get("custom_template/export_format")) == EXPORT_FORMAT_AAB &&
+ !bool(p_preset->get("custom_template/use_custom_build"))) {
+ valid = false;
+ err += TTR("\"Export AAB\" is only valid when \"Use Custom Build\" is enabled.");
+ err += "\n";
+ }
+
+ r_error = err;
+ return valid;
+}
+
+List<String> EditorExportPlatformAndroid::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
+ List<String> list;
+ list.push_back("apk");
+ list.push_back("aab");
+ return list;
+}
+
+String EditorExportPlatformAndroid::get_apk_expansion_fullpath(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+ int version_code = p_preset->get("version/code");
+ String package_name = p_preset->get("package/unique_name");
+ String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb";
+ String fullpath = p_path.get_base_dir().plus_file(apk_file_name);
+ return fullpath;
+}
+
+Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+ String fullpath = get_apk_expansion_fullpath(p_preset, p_path);
+ Error err = save_pack(p_preset, fullpath);
+ return err;
+}
+
+void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) {
+ String cmdline = p_preset->get("command_line/extra_args");
+ Vector<String> command_line_strings = cmdline.strip_edges().split(" ");
+ for (int i = 0; i < command_line_strings.size(); i++) {
+ if (command_line_strings[i].strip_edges().length() == 0) {
+ command_line_strings.remove(i);
+ i--;
+ }
+ }
+
+ gen_export_flags(command_line_strings, p_flags);
+
+ bool apk_expansion = p_preset->get("apk_expansion/enable");
+ if (apk_expansion) {
+ String fullpath = get_apk_expansion_fullpath(p_preset, p_path);
+ String apk_expansion_public_key = p_preset->get("apk_expansion/public_key");
+
+ command_line_strings.push_back("--use_apk_expansion");
+ command_line_strings.push_back("--apk_expansion_md5");
+ command_line_strings.push_back(FileAccess::get_md5(fullpath));
+ command_line_strings.push_back("--apk_expansion_key");
+ command_line_strings.push_back(apk_expansion_public_key.strip_edges());
+ }
+
+ int xr_mode_index = p_preset->get("xr_features/xr_mode");
+ if (xr_mode_index == 1) {
+ command_line_strings.push_back("--xr_mode_ovr");
+ } else { // XRMode.REGULAR is the default.
+ command_line_strings.push_back("--xr_mode_regular");
+ }
+
+ bool immersive = p_preset->get("screen/immersive_mode");
+ if (immersive) {
+ command_line_strings.push_back("--use_immersive");
+ }
+
+ bool debug_opengl = p_preset->get("graphics/opengl_debug");
+ if (debug_opengl) {
+ command_line_strings.push_back("--debug_opengl");
+ }
+
+ if (command_line_strings.size()) {
+ r_command_line_flags.resize(4);
+ encode_uint32(command_line_strings.size(), &r_command_line_flags.write[0]);
+ for (int i = 0; i < command_line_strings.size(); i++) {
+ print_line(itos(i) + " param: " + command_line_strings[i]);
+ CharString command_line_argument = command_line_strings[i].utf8();
+ int base = r_command_line_flags.size();
+ int length = command_line_argument.length();
+ if (length == 0) {
+ continue;
+ }
+ r_command_line_flags.resize(base + 4 + length);
+ encode_uint32(length, &r_command_line_flags.write[base]);
+ memcpy(&r_command_line_flags.write[base + 4], command_line_argument.ptr(), length);
+ }
+ }
+}
+
+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("custom_template/export_format"));
+ String export_label = export_format == EXPORT_FORMAT_AAB ? "AAB" : "APK";
+ String release_keystore = p_preset->get("keystore/release");
+ String release_username = p_preset->get("keystore/release_user");
+ String release_password = p_preset->get("keystore/release_password");
+
+ String apksigner = get_apksigner_path();
+ print_verbose("Starting signing of the " + export_label + " binary using " + apksigner);
+ if (!FileAccess::exists(apksigner)) {
+ EditorNode::add_io_error(vformat(TTR("'apksigner' could not be found.\nPlease check the command is available in the Android SDK build-tools directory.\nThe resulting %s is unsigned."), export_label));
+ return OK;
+ }
+
+ String keystore;
+ String password;
+ String user;
+ if (p_debug) {
+ keystore = p_preset->get("keystore/debug");
+ password = p_preset->get("keystore/debug_password");
+ user = p_preset->get("keystore/debug_user");
+
+ if (keystore.is_empty()) {
+ keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore");
+ password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass");
+ user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
+ }
+
+ if (ep.step(vformat(TTR("Signing debug %s..."), export_label), 104)) {
+ return ERR_SKIP;
+ }
+
+ } else {
+ keystore = release_keystore;
+ password = release_password;
+ user = release_username;
+
+ if (ep.step(vformat(TTR("Signing release %s..."), export_label), 104)) {
+ return ERR_SKIP;
+ }
+ }
+
+ if (!FileAccess::exists(keystore)) {
+ EditorNode::add_io_error(TTR("Could not find keystore, unable to export."));
+ return ERR_FILE_CANT_OPEN;
+ }
+
+ String output;
+ List<String> args;
+ args.push_back("sign");
+ args.push_back("--verbose");
+ args.push_back("--ks");
+ args.push_back(keystore);
+ args.push_back("--ks-pass");
+ args.push_back("pass:" + password);
+ args.push_back("--ks-key-alias");
+ args.push_back(user);
+ args.push_back(export_path);
+ if (p_debug) {
+ // We only print verbose logs for debug builds to avoid leaking release keystore credentials.
+ print_verbose("Signing debug binary using: " + String("\n") + apksigner + " " + join_list(args, String(" ")));
+ }
+ int retval;
+ output.clear();
+ OS::get_singleton()->execute(apksigner, args, &output, &retval, true);
+ print_verbose(output);
+ if (retval) {
+ EditorNode::add_io_error(vformat(TTR("'apksigner' returned with error #%d"), retval));
+ return ERR_CANT_CREATE;
+ }
+
+ if (ep.step(vformat(TTR("Verifying %s..."), export_label), 105)) {
+ return ERR_SKIP;
+ }
+
+ args.clear();
+ args.push_back("verify");
+ args.push_back("--verbose");
+ args.push_back(export_path);
+ if (p_debug) {
+ print_verbose("Verifying signed build using: " + String("\n") + apksigner + " " + join_list(args, String(" ")));
+ }
+
+ output.clear();
+ OS::get_singleton()->execute(apksigner, args, &output, &retval, true);
+ print_verbose(output);
+ if (retval) {
+ EditorNode::add_io_error(vformat(TTR("'apksigner' verification of %s failed."), export_label));
+ return ERR_CANT_CREATE;
+ }
+
+ print_verbose("Successfully completed signing build.");
+ return OK;
+}
+
+void EditorExportPlatformAndroid::_clear_assets_directory() {
+ DirAccessRef da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+
+ // Clear the APK assets directory
+ if (da_res->dir_exists(APK_ASSETS_DIRECTORY)) {
+ print_verbose("Clearing APK assets directory..");
+ DirAccessRef da_assets = DirAccess::open(APK_ASSETS_DIRECTORY);
+ da_assets->erase_contents_recursive();
+ da_res->remove(APK_ASSETS_DIRECTORY);
+ }
+
+ // Clear the AAB assets directory
+ if (da_res->dir_exists(AAB_ASSETS_DIRECTORY)) {
+ print_verbose("Clearing AAB assets directory..");
+ DirAccessRef da_assets = DirAccess::open(AAB_ASSETS_DIRECTORY);
+ da_assets->erase_contents_recursive();
+ da_res->remove(AAB_ASSETS_DIRECTORY);
+ }
+}
+
+void EditorExportPlatformAndroid::_remove_copied_libs() {
+ print_verbose("Removing previously installed libraries...");
+ Error error;
+ String libs_json = FileAccess::get_file_as_string(GDNATIVE_LIBS_PATH, &error);
+ if (error || libs_json.is_empty()) {
+ print_verbose("No previously installed libraries found");
+ return;
+ }
+
+ JSON json;
+ error = json.parse(libs_json);
+ ERR_FAIL_COND_MSG(error, "Error parsing \"" + libs_json + "\" on line " + itos(json.get_error_line()) + ": " + json.get_error_message());
+
+ Vector<String> libs = json.get_data();
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ for (int i = 0; i < libs.size(); i++) {
+ print_verbose("Removing previously installed library " + libs[i]);
+ da->remove(libs[i]);
+ }
+ da->remove(GDNATIVE_LIBS_PATH);
+}
+
+String EditorExportPlatformAndroid::join_list(List<String> parts, const String &separator) const {
+ String ret;
+ for (int i = 0; i < parts.size(); ++i) {
+ if (i > 0) {
+ ret += separator;
+ }
+ ret += parts[i];
+ }
+ return ret;
+}
+
+Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+ int export_format = int(p_preset->get("custom_template/export_format"));
+ bool should_sign = p_preset->get("package/signed");
+ return export_project_helper(p_preset, p_debug, p_path, export_format, should_sign, p_flags);
+}
+
+Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ String src_apk;
+ Error err;
+
+ EditorProgress ep("export", TTR("Exporting for Android"), 105, true);
+
+ bool use_custom_build = bool(p_preset->get("custom_template/use_custom_build"));
+ bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG);
+ bool apk_expansion = p_preset->get("apk_expansion/enable");
+ Vector<String> enabled_abis = get_enabled_abis(p_preset);
+
+ print_verbose("Exporting for Android...");
+ print_verbose("- debug build: " + bool_to_string(p_debug));
+ print_verbose("- export path: " + p_path);
+ print_verbose("- export format: " + itos(export_format));
+ print_verbose("- sign build: " + bool_to_string(should_sign));
+ print_verbose("- custom build enabled: " + bool_to_string(use_custom_build));
+ print_verbose("- apk expansion enabled: " + bool_to_string(apk_expansion));
+ print_verbose("- enabled abis: " + String(",").join(enabled_abis));
+ print_verbose("- export filter: " + itos(p_preset->get_export_filter()));
+ print_verbose("- include filter: " + p_preset->get_include_filter());
+ print_verbose("- exclude filter: " + p_preset->get_exclude_filter());
+
+ Ref<Image> splash_image;
+ Ref<Image> splash_bg_color_image;
+ String processed_splash_config_xml = load_splash_refs(splash_image, splash_bg_color_image);
+
+ Ref<Image> main_image;
+ Ref<Image> foreground;
+ Ref<Image> background;
+
+ load_icon_refs(p_preset, main_image, foreground, background);
+
+ Vector<uint8_t> command_line_flags;
+ // Write command line flags into the command_line_flags variable.
+ get_command_line_flags(p_preset, p_path, p_flags, command_line_flags);
+
+ if (export_format == EXPORT_FORMAT_AAB) {
+ if (!p_path.ends_with(".aab")) {
+ EditorNode::get_singleton()->show_warning(TTR("Invalid filename! Android App Bundle requires the *.aab extension."));
+ return ERR_UNCONFIGURED;
+ }
+ if (apk_expansion) {
+ EditorNode::get_singleton()->show_warning(TTR("APK Expansion not compatible with Android App Bundle."));
+ return ERR_UNCONFIGURED;
+ }
+ }
+ if (export_format == EXPORT_FORMAT_APK && !p_path.ends_with(".apk")) {
+ EditorNode::get_singleton()->show_warning(
+ TTR("Invalid filename! Android APK requires the *.apk extension."));
+ return ERR_UNCONFIGURED;
+ }
+ if (export_format > EXPORT_FORMAT_AAB || export_format < EXPORT_FORMAT_APK) {
+ EditorNode::add_io_error(TTR("Unsupported export format!\n"));
+ return ERR_UNCONFIGURED; //TODO: is this the right error?
+ }
+
+ if (use_custom_build) {
+ print_verbose("Starting custom build..");
+ //test that installed build version is alright
+ {
+ print_verbose("Checking build version..");
+ FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ);
+ if (!f) {
+ EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu."));
+ return ERR_UNCONFIGURED;
+ }
+ String version = f->get_line().strip_edges();
+ print_verbose("- build version: " + version);
+ f->close();
+ if (version != VERSION_FULL_CONFIG) {
+ EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG));
+ return ERR_UNCONFIGURED;
+ }
+ }
+ const String assets_directory = get_assets_directory(p_preset, export_format);
+ String sdk_path = EDITOR_GET("export/android/android_sdk_path");
+ ERR_FAIL_COND_V_MSG(sdk_path.is_empty(), ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/android_sdk_path'.");
+ print_verbose("Android sdk path: " + sdk_path);
+
+ // TODO: should we use "package/name" or "application/config/name"?
+ String project_name = get_project_name(p_preset->get("package/name"));
+ err = _create_project_name_strings_files(p_preset, project_name); //project name localization.
+ if (err != OK) {
+ EditorNode::add_io_error(TTR("Unable to overwrite res://android/build/res/*.xml files with project name"));
+ }
+ // Copies the project icon files into the appropriate Gradle project directory.
+ _copy_icons_to_gradle_project(p_preset, processed_splash_config_xml, splash_image, splash_bg_color_image, main_image, foreground, background);
+ // Write an AndroidManifest.xml file into the Gradle project directory.
+ _write_tmp_manifest(p_preset, p_give_internet, p_debug);
+
+ //stores all the project files inside the Gradle project directory. Also includes all ABIs
+ _clear_assets_directory();
+ _remove_copied_libs();
+ if (!apk_expansion) {
+ print_verbose("Exporting project files..");
+ CustomExportData user_data;
+ user_data.assets_directory = assets_directory;
+ user_data.debug = p_debug;
+ err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so);
+ if (err != OK) {
+ EditorNode::add_io_error(TTR("Could not export project files to gradle project\n"));
+ return err;
+ }
+ if (user_data.libs.size() > 0) {
+ FileAccessRef fa = FileAccess::open(GDNATIVE_LIBS_PATH, FileAccess::WRITE);
+ JSON json;
+ fa->store_string(json.stringify(user_data.libs, "\t"));
+ fa->close();
+ }
+ } else {
+ print_verbose("Saving apk expansion file..");
+ err = save_apk_expansion_file(p_preset, p_path);
+ if (err != OK) {
+ EditorNode::add_io_error(TTR("Could not write expansion package file!"));
+ return err;
+ }
+ }
+ print_verbose("Storing command line flags..");
+ store_file_at_path(assets_directory + "/_cl_", command_line_flags);
+
+ print_verbose("Updating ANDROID_HOME environment to " + sdk_path);
+ OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required
+ String build_command;
+
+#ifdef WINDOWS_ENABLED
+ build_command = "gradlew.bat";
+#else
+ build_command = "gradlew";
+#endif
+
+ String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build");
+ build_command = build_path.plus_file(build_command);
+
+ String package_name = get_package_name(p_preset->get("package/unique_name"));
+ String version_code = itos(p_preset->get("version/code"));
+ String version_name = p_preset->get("version/name");
+ String enabled_abi_string = String("|").join(enabled_abis);
+ String sign_flag = should_sign ? "true" : "false";
+ String zipalign_flag = "true";
+
+ Vector<PluginConfigAndroid> enabled_plugins = get_enabled_plugins(p_preset);
+ String local_plugins_binaries = PluginConfigAndroid::get_plugins_binaries(PluginConfigAndroid::BINARY_TYPE_LOCAL, enabled_plugins);
+ String remote_plugins_binaries = PluginConfigAndroid::get_plugins_binaries(PluginConfigAndroid::BINARY_TYPE_REMOTE, enabled_plugins);
+ String custom_maven_repos = PluginConfigAndroid::get_plugins_custom_maven_repos(enabled_plugins);
+ bool clean_build_required = is_clean_build_required(enabled_plugins);
+
+ List<String> cmdline;
+ if (clean_build_required) {
+ cmdline.push_back("clean");
+ }
+
+ String build_type = p_debug ? "Debug" : "Release";
+ if (export_format == EXPORT_FORMAT_AAB) {
+ String bundle_build_command = vformat("bundle%s", build_type);
+ cmdline.push_back(bundle_build_command);
+ } else if (export_format == EXPORT_FORMAT_APK) {
+ String apk_build_command = vformat("assemble%s", build_type);
+ cmdline.push_back(apk_build_command);
+ }
+
+ cmdline.push_back("-p"); // argument to specify the start directory.
+ cmdline.push_back(build_path); // start directory.
+ cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
+ cmdline.push_back("-Pexport_version_code=" + version_code); // argument to specify the version code.
+ cmdline.push_back("-Pexport_version_name=" + version_name); // argument to specify the version name.
+ cmdline.push_back("-Pexport_enabled_abis=" + enabled_abi_string); // argument to specify enabled ABIs.
+ cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies.
+ cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies.
+ cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies.
+ cmdline.push_back("-Pperform_zipalign=" + zipalign_flag); // argument to specify whether the build should be zipaligned.
+ cmdline.push_back("-Pperform_signing=" + sign_flag); // argument to specify whether the build should be signed.
+ cmdline.push_back("-Pgodot_editor_version=" + String(VERSION_FULL_CONFIG));
+
+ // NOTE: The release keystore is not included in the verbose logging
+ // to avoid accidentally leaking sensitive information when sharing verbose logs for troubleshooting.
+ // Any non-sensitive additions to the command line arguments must be done above this section.
+ // Sensitive additions must be done below the logging statement.
+ print_verbose("Build Android project using gradle command: " + String("\n") + build_command + " " + join_list(cmdline, String(" ")));
+
+ if (should_sign) {
+ if (p_debug) {
+ String debug_keystore = p_preset->get("keystore/debug");
+ String debug_password = p_preset->get("keystore/debug_password");
+ String debug_user = p_preset->get("keystore/debug_user");
+
+ if (debug_keystore.is_empty()) {
+ debug_keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore");
+ debug_password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass");
+ debug_user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
+ }
+
+ cmdline.push_back("-Pdebug_keystore_file=" + debug_keystore); // argument to specify the debug keystore file.
+ cmdline.push_back("-Pdebug_keystore_alias=" + debug_user); // argument to specify the debug keystore alias.
+ cmdline.push_back("-Pdebug_keystore_password=" + debug_password); // argument to specify the debug keystore password.
+ } else {
+ // Pass the release keystore info as well
+ String release_keystore = p_preset->get("keystore/release");
+ String release_username = p_preset->get("keystore/release_user");
+ String release_password = p_preset->get("keystore/release_password");
+ if (!FileAccess::exists(release_keystore)) {
+ EditorNode::add_io_error(TTR("Could not find keystore, unable to export."));
+ return ERR_FILE_CANT_OPEN;
+ }
+
+ cmdline.push_back("-Prelease_keystore_file=" + release_keystore); // argument to specify the release keystore file.
+ cmdline.push_back("-Prelease_keystore_alias=" + release_username); // argument to specify the release keystore alias.
+ cmdline.push_back("-Prelease_keystore_password=" + release_password); // argument to specify the release keystore password.
+ }
+ }
+
+ int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline);
+ if (result != 0) {
+ EditorNode::get_singleton()->show_warning(TTR("Building of Android project failed, check output for the error.\nAlternatively visit docs.godotengine.org for Android build documentation."));
+ return ERR_CANT_CREATE;
+ }
+
+ List<String> copy_args;
+ String copy_command;
+ if (export_format == EXPORT_FORMAT_AAB) {
+ copy_command = vformat("copyAndRename%sAab", build_type);
+ } else if (export_format == EXPORT_FORMAT_APK) {
+ copy_command = vformat("copyAndRename%sApk", build_type);
+ }
+
+ copy_args.push_back(copy_command);
+
+ copy_args.push_back("-p"); // argument to specify the start directory.
+ copy_args.push_back(build_path); // start directory.
+
+ String export_filename = p_path.get_file();
+ String export_path = p_path.get_base_dir();
+ if (export_path.is_relative_path()) {
+ export_path = OS::get_singleton()->get_resource_dir().plus_file(export_path);
+ }
+ export_path = ProjectSettings::get_singleton()->globalize_path(export_path).simplify_path();
+
+ copy_args.push_back("-Pexport_path=file:" + export_path);
+ copy_args.push_back("-Pexport_filename=" + export_filename);
+
+ print_verbose("Copying Android binary using gradle command: " + String("\n") + build_command + " " + join_list(copy_args, String(" ")));
+ int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args);
+ if (copy_result != 0) {
+ EditorNode::get_singleton()->show_warning(TTR("Unable to copy and rename export file, check gradle project directory for outputs."));
+ return ERR_CANT_CREATE;
+ }
+
+ print_verbose("Successfully completed Android custom build.");
+ return OK;
+ }
+ // This is the start of the Legacy build system
+ print_verbose("Starting legacy build system..");
+ if (p_debug) {
+ src_apk = p_preset->get("custom_template/debug");
+ } else {
+ src_apk = p_preset->get("custom_template/release");
+ }
+ src_apk = src_apk.strip_edges();
+ if (src_apk == "") {
+ if (p_debug) {
+ src_apk = find_export_template("android_debug.apk");
+ } else {
+ src_apk = find_export_template("android_release.apk");
+ }
+ if (src_apk == "") {
+ EditorNode::add_io_error(vformat(TTR("Package not found: %s"), src_apk));
+ return ERR_FILE_NOT_FOUND;
+ }
+ }
+
+ if (!DirAccess::exists(p_path.get_base_dir())) {
+ return ERR_FILE_BAD_PATH;
+ }
+
+ FileAccess *src_f = nullptr;
+ zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
+
+ if (ep.step(TTR("Creating APK..."), 0)) {
+ return ERR_SKIP;
+ }
+
+ unzFile pkg = unzOpen2(src_apk.utf8().get_data(), &io);
+ if (!pkg) {
+ EditorNode::add_io_error(vformat(TTR("Could not find template APK to export:\n%s"), src_apk));
+ return ERR_FILE_NOT_FOUND;
+ }
+
+ int ret = unzGoToFirstFile(pkg);
+
+ zlib_filefunc_def io2 = io;
+ FileAccess *dst_f = nullptr;
+ io2.opaque = &dst_f;
+
+ String tmp_unaligned_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned." + uitos(OS::get_singleton()->get_unix_time()) + ".apk");
+
+#define CLEANUP_AND_RETURN(m_err) \
+ { \
+ DirAccess::remove_file_or_error(tmp_unaligned_path); \
+ return m_err; \
+ }
+
+ zipFile unaligned_apk = zipOpen2(tmp_unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2);
+
+ String cmdline = p_preset->get("command_line/extra_args");
+
+ String version_name = p_preset->get("version/name");
+ String package_name = p_preset->get("package/unique_name");
+
+ String apk_expansion_pkey = p_preset->get("apk_expansion/public_key");
+
+ Vector<String> invalid_abis(enabled_abis);
+ while (ret == UNZ_OK) {
+ //get filename
+ unz_file_info info;
+ char fname[16384];
+ ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
+
+ bool skip = false;
+
+ String file = fname;
+
+ Vector<uint8_t> data;
+ data.resize(info.uncompressed_size);
+
+ //read
+ unzOpenCurrentFile(pkg);
+ unzReadCurrentFile(pkg, data.ptrw(), data.size());
+ unzCloseCurrentFile(pkg);
+
+ //write
+ if (file == "AndroidManifest.xml") {
+ _fix_manifest(p_preset, data, p_give_internet);
+ }
+ if (file == "resources.arsc") {
+ _fix_resources(p_preset, data);
+ }
+
+ // Process the splash image
+ if ((file == SPLASH_IMAGE_EXPORT_PATH || file == LEGACY_BUILD_SPLASH_IMAGE_EXPORT_PATH) && splash_image.is_valid() && !splash_image->is_empty()) {
+ _load_image_data(splash_image, data);
+ }
+
+ // Process the splash bg color image
+ if ((file == SPLASH_BG_COLOR_PATH || file == LEGACY_BUILD_SPLASH_BG_COLOR_PATH) && splash_bg_color_image.is_valid() && !splash_bg_color_image->is_empty()) {
+ _load_image_data(splash_bg_color_image, data);
+ }
+
+ for (int i = 0; i < icon_densities_count; ++i) {
+ if (main_image.is_valid() && !main_image->is_empty()) {
+ if (file == launcher_icons[i].export_path) {
+ _process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data);
+ }
+ }
+ if (foreground.is_valid() && !foreground->is_empty()) {
+ if (file == launcher_adaptive_icon_foregrounds[i].export_path) {
+ _process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data);
+ }
+ }
+ if (background.is_valid() && !background->is_empty()) {
+ if (file == launcher_adaptive_icon_backgrounds[i].export_path) {
+ _process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data);
+ }
+ }
+ }
+
+ if (file.ends_with(".so")) {
+ bool enabled = false;
+ for (int i = 0; i < enabled_abis.size(); ++i) {
+ if (file.begins_with("lib/" + enabled_abis[i] + "/")) {
+ invalid_abis.erase(enabled_abis[i]);
+ enabled = true;
+ break;
+ }
+ }
+ if (!enabled) {
+ skip = true;
+ }
+ }
+
+ if (file.begins_with("META-INF") && should_sign) {
+ skip = true;
+ }
+
+ if (!skip) {
+ print_line("ADDING: " + file);
+
+ // Respect decision on compression made by AAPT for the export template
+ const bool uncompressed = info.compression_method == 0;
+
+ zip_fileinfo zipfi = get_zip_fileinfo();
+
+ zipOpenNewFileInZip(unaligned_apk,
+ file.utf8().get_data(),
+ &zipfi,
+ nullptr,
+ 0,
+ nullptr,
+ 0,
+ nullptr,
+ uncompressed ? 0 : Z_DEFLATED,
+ Z_DEFAULT_COMPRESSION);
+
+ zipWriteInFileInZip(unaligned_apk, data.ptr(), data.size());
+ zipCloseFileInZip(unaligned_apk);
+ }
+
+ ret = unzGoToNextFile(pkg);
+ }
+
+ if (!invalid_abis.is_empty()) {
+ String unsupported_arch = String(", ").join(invalid_abis);
+ EditorNode::add_io_error(vformat(TTR("Missing libraries in the export template for the selected architectures: %s.\nPlease build a template with all required libraries, or uncheck the missing architectures in the export preset."), unsupported_arch));
+ CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND);
+ }
+
+ if (ep.step(TTR("Adding files..."), 1)) {
+ CLEANUP_AND_RETURN(ERR_SKIP);
+ }
+ err = OK;
+
+ if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ APKExportData ed;
+ ed.ep = &ep;
+ ed.apk = unaligned_apk;
+ err = export_project_files(p_preset, ignore_apk_file, &ed, save_apk_so);
+ } else {
+ if (apk_expansion) {
+ err = save_apk_expansion_file(p_preset, p_path);
+ if (err != OK) {
+ EditorNode::add_io_error(TTR("Could not write expansion package file!"));
+ return err;
+ }
+ } else {
+ APKExportData ed;
+ ed.ep = &ep;
+ ed.apk = unaligned_apk;
+ err = export_project_files(p_preset, save_apk_file, &ed, save_apk_so);
+ }
+ }
+
+ if (err != OK) {
+ unzClose(pkg);
+ EditorNode::add_io_error(TTR("Could not export project files"));
+ CLEANUP_AND_RETURN(ERR_SKIP);
+ }
+
+ zip_fileinfo zipfi = get_zip_fileinfo();
+ zipOpenNewFileInZip(unaligned_apk,
+ "assets/_cl_",
+ &zipfi,
+ nullptr,
+ 0,
+ nullptr,
+ 0,
+ nullptr,
+ 0, // No compress (little size gain and potentially slower startup)
+ Z_DEFAULT_COMPRESSION);
+ zipWriteInFileInZip(unaligned_apk, command_line_flags.ptr(), command_line_flags.size());
+ zipCloseFileInZip(unaligned_apk);
+ zipClose(unaligned_apk, nullptr);
+ unzClose(pkg);
+
+ if (err != OK) {
+ CLEANUP_AND_RETURN(err);
+ }
+
+ // Let's zip-align (must be done before signing)
+
+ static const int ZIP_ALIGNMENT = 4;
+
+ // If we're not signing the apk, then the next step should be the last.
+ const int next_step = should_sign ? 103 : 105;
+ if (ep.step(TTR("Aligning APK..."), next_step)) {
+ CLEANUP_AND_RETURN(ERR_SKIP);
+ }
+
+ unzFile tmp_unaligned = unzOpen2(tmp_unaligned_path.utf8().get_data(), &io);
+ if (!tmp_unaligned) {
+ EditorNode::add_io_error(TTR("Could not unzip temporary unaligned APK."));
+ CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND);
+ }
+
+ ret = unzGoToFirstFile(tmp_unaligned);
+
+ io2 = io;
+ dst_f = nullptr;
+ io2.opaque = &dst_f;
+ zipFile final_apk = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2);
+
+ // Take files from the unaligned APK and write them out to the aligned one
+ // in raw mode, i.e. not uncompressing and recompressing, aligning them as needed,
+ // following what is done in https://github.com/android/platform_build/blob/master/tools/zipalign/ZipAlign.cpp
+ int bias = 0;
+ while (ret == UNZ_OK) {
+ unz_file_info info;
+ memset(&info, 0, sizeof(info));
+
+ char fname[16384];
+ char extra[16384];
+ ret = unzGetCurrentFileInfo(tmp_unaligned, &info, fname, 16384, extra, 16384 - ZIP_ALIGNMENT, nullptr, 0);
+
+ String file = fname;
+
+ Vector<uint8_t> data;
+ data.resize(info.compressed_size);
+
+ // read
+ int method, level;
+ unzOpenCurrentFile2(tmp_unaligned, &method, &level, 1); // raw read
+ long file_offset = unzGetCurrentFileZStreamPos64(tmp_unaligned);
+ unzReadCurrentFile(tmp_unaligned, data.ptrw(), data.size());
+ unzCloseCurrentFile(tmp_unaligned);
+
+ // align
+ int padding = 0;
+ if (!info.compression_method) {
+ // Uncompressed file => Align
+ long new_offset = file_offset + bias;
+ padding = (ZIP_ALIGNMENT - (new_offset % ZIP_ALIGNMENT)) % ZIP_ALIGNMENT;
+ }
+
+ memset(extra + info.size_file_extra, 0, padding);
+
+ zip_fileinfo fileinfo = get_zip_fileinfo();
+ zipOpenNewFileInZip2(final_apk,
+ file.utf8().get_data(),
+ &fileinfo,
+ extra,
+ info.size_file_extra + padding,
+ nullptr,
+ 0,
+ nullptr,
+ method,
+ level,
+ 1); // raw write
+ zipWriteInFileInZip(final_apk, data.ptr(), data.size());
+ zipCloseFileInZipRaw(final_apk, info.uncompressed_size, info.crc);
+
+ bias += padding;
+
+ ret = unzGoToNextFile(tmp_unaligned);
+ }
+
+ zipClose(final_apk, nullptr);
+ unzClose(tmp_unaligned);
+
+ if (should_sign) {
+ // Signing must be done last as any additional modifications to the
+ // file will invalidate the signature.
+ err = sign_apk(p_preset, p_debug, p_path, ep);
+ if (err != OK) {
+ CLEANUP_AND_RETURN(err);
+ }
+ }
+
+ CLEANUP_AND_RETURN(OK);
+}
+
+void EditorExportPlatformAndroid::get_platform_features(List<String> *r_features) {
+ r_features->push_back("mobile");
+ r_features->push_back("android");
+}
+
+void EditorExportPlatformAndroid::resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) {
+}
+
+EditorExportPlatformAndroid::EditorExportPlatformAndroid() {
+ Ref<Image> img = memnew(Image(_android_logo));
+ logo.instantiate();
+ logo->create_from_image(img);
+
+ img = Ref<Image>(memnew(Image(_android_run_icon)));
+ run_icon.instantiate();
+ run_icon->create_from_image(img);
+
+ devices_changed.set();
+ plugins_changed.set();
+ check_for_changes_thread.start(_check_for_changes_poll_thread, this);
+}
+
+EditorExportPlatformAndroid::~EditorExportPlatformAndroid() {
+ quit_request.set();
+ check_for_changes_thread.wait_to_finish();
+}
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
new file mode 100644
index 0000000000..e0ffaa718b
--- /dev/null
+++ b/platform/android/export/export_plugin.h
@@ -0,0 +1,254 @@
+/*************************************************************************/
+/* export_plugin.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "core/config/project_settings.h"
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
+#include "core/io/image_loader.h"
+#include "core/io/json.h"
+#include "core/io/marshalls.h"
+#include "core/io/zip_io.h"
+#include "core/os/os.h"
+#include "core/templates/safe_refcount.h"
+#include "core/version.h"
+#include "drivers/png/png_driver_common.h"
+#include "editor/editor_export.h"
+#include "editor/editor_log.h"
+#include "editor/editor_node.h"
+#include "editor/editor_settings.h"
+#include "main/splash.gen.h"
+#include "platform/android/logo.gen.h"
+#include "platform/android/run_icon.gen.h"
+
+#include "godot_plugin_config.h"
+#include "gradle_export_util.h"
+
+#include <string.h>
+
+const String SPLASH_CONFIG_XML_CONTENT = R"SPLASH(<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/splash_bg_color" />
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:filter="%s"
+ android:src="@drawable/splash" />
+ </item>
+</layer-list>
+)SPLASH";
+
+struct LauncherIcon {
+ const char *export_path;
+ int dimensions = 0;
+};
+
+class EditorExportPlatformAndroid : public EditorExportPlatform {
+ GDCLASS(EditorExportPlatformAndroid, EditorExportPlatform);
+
+ Ref<ImageTexture> logo;
+ Ref<ImageTexture> run_icon;
+
+ struct Device {
+ String id;
+ String name;
+ String description;
+ int api_level = 0;
+ };
+
+ struct APKExportData {
+ zipFile apk;
+ EditorProgress *ep = nullptr;
+ };
+
+ Vector<PluginConfigAndroid> plugins;
+ String last_plugin_names;
+ uint64_t last_custom_build_time = 0;
+ SafeFlag plugins_changed;
+ Mutex plugins_lock;
+ Vector<Device> devices;
+ SafeFlag devices_changed;
+ Mutex device_lock;
+ Thread check_for_changes_thread;
+ SafeFlag quit_request;
+
+ static void _check_for_changes_poll_thread(void *ud);
+
+ String get_project_name(const String &p_name) const;
+
+ String get_package_name(const String &p_package) const;
+
+ String get_assets_directory(const Ref<EditorExportPreset> &p_preset, int p_export_format) const;
+
+ bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const;
+
+ static bool _should_compress_asset(const String &p_path, const Vector<uint8_t> &p_data);
+
+ static zip_fileinfo get_zip_fileinfo();
+
+ static Vector<String> get_abis();
+
+ /// List the gdap files in the directory specified by the p_path parameter.
+ static Vector<String> list_gdap_files(const String &p_path);
+
+ static Vector<PluginConfigAndroid> get_plugins();
+
+ static Vector<PluginConfigAndroid> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets);
+
+ static Error store_in_apk(APKExportData *ed, const String &p_path, const Vector<uint8_t> &p_data, int compression_method = Z_DEFLATED);
+
+ static Error save_apk_so(void *p_userdata, const SharedObject &p_so);
+
+ static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+
+ static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+
+ static Error copy_gradle_so(void *p_userdata, const SharedObject &p_so);
+
+ bool _has_storage_permission(const Vector<String> &p_permissions);
+
+ void _get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions);
+
+ void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug);
+
+ void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet);
+
+ static String _parse_string(const uint8_t *p_bytes, bool p_utf8);
+
+ void _fix_resources(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &r_manifest);
+
+ void _load_image_data(const Ref<Image> &p_splash_image, Vector<uint8_t> &p_data);
+
+ void _process_launcher_icons(const String &p_file_name, const Ref<Image> &p_source_image, int dimension, Vector<uint8_t> &p_data);
+
+ String load_splash_refs(Ref<Image> &splash_image, Ref<Image> &splash_bg_color_image);
+
+ void load_icon_refs(const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background);
+
+ void store_image(const LauncherIcon launcher_icon, const Vector<uint8_t> &data);
+
+ void store_image(const String &export_path, const Vector<uint8_t> &data);
+
+ void _copy_icons_to_gradle_project(const Ref<EditorExportPreset> &p_preset,
+ const String &processed_splash_config_xml,
+ const Ref<Image> &splash_image,
+ const Ref<Image> &splash_bg_color_image,
+ const Ref<Image> &main_image,
+ const Ref<Image> &foreground,
+ const Ref<Image> &background);
+
+ static Vector<String> get_enabled_abis(const Ref<EditorExportPreset> &p_preset);
+
+public:
+ typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+
+public:
+ virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override;
+
+ virtual void get_export_options(List<ExportOption> *r_options) override;
+
+ virtual String get_name() const override;
+
+ virtual String get_os_name() const override;
+
+ virtual Ref<Texture2D> get_logo() const override;
+
+ virtual bool should_update_export_options() override;
+
+ virtual bool poll_export() override;
+
+ virtual int get_options_count() const override;
+
+ virtual String get_options_tooltip() const override;
+
+ virtual String get_option_label(int p_index) const override;
+
+ virtual String get_option_tooltip(int p_index) const override;
+
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+
+ virtual Ref<Texture2D> get_run_icon() const override;
+
+ static String get_adb_path();
+
+ static String get_apksigner_path();
+
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+
+ virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
+
+ inline bool is_clean_build_required(Vector<PluginConfigAndroid> enabled_plugins) {
+ String plugin_names = PluginConfigAndroid::get_plugins_names(enabled_plugins);
+ bool first_build = last_custom_build_time == 0;
+ bool have_plugins_changed = false;
+
+ if (!first_build) {
+ have_plugins_changed = plugin_names != last_plugin_names;
+ if (!have_plugins_changed) {
+ for (int i = 0; i < enabled_plugins.size(); i++) {
+ if (enabled_plugins.get(i).last_updated > last_custom_build_time) {
+ have_plugins_changed = true;
+ break;
+ }
+ }
+ }
+ }
+
+ last_custom_build_time = OS::get_singleton()->get_unix_time();
+ last_plugin_names = plugin_names;
+
+ return have_plugins_changed || first_build;
+ }
+
+ String get_apk_expansion_fullpath(const Ref<EditorExportPreset> &p_preset, const String &p_path);
+
+ Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, const String &p_path);
+
+ void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags);
+
+ Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep);
+
+ void _clear_assets_directory();
+
+ void _remove_copied_libs();
+
+ String join_list(List<String> parts, const String &separator) const;
+
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+
+ Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags);
+
+ virtual void get_platform_features(List<String> *r_features) override;
+
+ virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override;
+
+ EditorExportPlatformAndroid();
+
+ ~EditorExportPlatformAndroid();
+};
diff --git a/platform/android/export/godot_plugin_config.cpp b/platform/android/export/godot_plugin_config.cpp
new file mode 100644
index 0000000000..205cba3350
--- /dev/null
+++ b/platform/android/export/godot_plugin_config.cpp
@@ -0,0 +1,211 @@
+/*************************************************************************/
+/* godot_plugin_config.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "godot_plugin_config.h"
+/*
+ * Set of prebuilt plugins.
+ * Currently unused, this is just for future reference:
+ */
+// static const PluginConfigAndroid MY_PREBUILT_PLUGIN = {
+// /*.valid_config =*/true,
+// /*.last_updated =*/0,
+// /*.name =*/"GodotPayment",
+// /*.binary_type =*/"local",
+// /*.binary =*/"res://android/build/libs/plugins/GodotPayment.release.aar",
+// /*.local_dependencies =*/{},
+// /*.remote_dependencies =*/String("com.android.billingclient:billing:2.2.1").split("|"),
+// /*.custom_maven_repos =*/{}
+// };
+
+String PluginConfigAndroid::resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
+ String absolute_path;
+ if (!dependency_path.is_empty()) {
+ if (dependency_path.is_absolute_path()) {
+ absolute_path = ProjectSettings::get_singleton()->globalize_path(dependency_path);
+ } else {
+ absolute_path = plugin_config_dir.plus_file(dependency_path);
+ }
+ }
+
+ return absolute_path;
+}
+
+PluginConfigAndroid PluginConfigAndroid::resolve_prebuilt_plugin(PluginConfigAndroid prebuilt_plugin, String plugin_config_dir) {
+ PluginConfigAndroid resolved = prebuilt_plugin;
+ resolved.binary = resolved.binary_type == PluginConfigAndroid::BINARY_TYPE_LOCAL ? resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.binary) : prebuilt_plugin.binary;
+ if (!prebuilt_plugin.local_dependencies.is_empty()) {
+ resolved.local_dependencies.clear();
+ for (int i = 0; i < prebuilt_plugin.local_dependencies.size(); i++) {
+ resolved.local_dependencies.push_back(resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.local_dependencies[i]));
+ }
+ }
+ return resolved;
+}
+
+Vector<PluginConfigAndroid> PluginConfigAndroid::get_prebuilt_plugins(String plugins_base_dir) {
+ Vector<PluginConfigAndroid> prebuilt_plugins;
+ // prebuilt_plugins.push_back(resolve_prebuilt_plugin(MY_PREBUILT_PLUGIN, plugins_base_dir));
+ return prebuilt_plugins;
+}
+
+bool PluginConfigAndroid::is_plugin_config_valid(PluginConfigAndroid plugin_config) {
+ bool valid_name = !plugin_config.name.is_empty();
+ bool valid_binary_type = plugin_config.binary_type == PluginConfigAndroid::BINARY_TYPE_LOCAL ||
+ plugin_config.binary_type == PluginConfigAndroid::BINARY_TYPE_REMOTE;
+
+ bool valid_binary = false;
+ if (valid_binary_type) {
+ valid_binary = !plugin_config.binary.is_empty() &&
+ (plugin_config.binary_type == PluginConfigAndroid::BINARY_TYPE_REMOTE ||
+ FileAccess::exists(plugin_config.binary));
+ }
+
+ bool valid_local_dependencies = true;
+ if (!plugin_config.local_dependencies.is_empty()) {
+ for (int i = 0; i < plugin_config.local_dependencies.size(); i++) {
+ if (!FileAccess::exists(plugin_config.local_dependencies[i])) {
+ valid_local_dependencies = false;
+ break;
+ }
+ }
+ }
+ return valid_name && valid_binary && valid_binary_type && valid_local_dependencies;
+}
+
+uint64_t PluginConfigAndroid::get_plugin_modification_time(const PluginConfigAndroid &plugin_config, const String &config_path) {
+ uint64_t last_updated = FileAccess::get_modified_time(config_path);
+ last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary));
+
+ for (int i = 0; i < plugin_config.local_dependencies.size(); i++) {
+ String binary = plugin_config.local_dependencies.get(i);
+ last_updated = MAX(last_updated, FileAccess::get_modified_time(binary));
+ }
+
+ return last_updated;
+}
+
+PluginConfigAndroid PluginConfigAndroid::load_plugin_config(Ref<ConfigFile> config_file, const String &path) {
+ PluginConfigAndroid plugin_config = {};
+
+ if (config_file.is_valid()) {
+ Error err = config_file->load(path);
+ if (err == OK) {
+ String config_base_dir = path.get_base_dir();
+
+ plugin_config.name = config_file->get_value(PluginConfigAndroid::CONFIG_SECTION, PluginConfigAndroid::CONFIG_NAME_KEY, String());
+ plugin_config.binary_type = config_file->get_value(PluginConfigAndroid::CONFIG_SECTION, PluginConfigAndroid::CONFIG_BINARY_TYPE_KEY, String());
+
+ String binary_path = config_file->get_value(PluginConfigAndroid::CONFIG_SECTION, PluginConfigAndroid::CONFIG_BINARY_KEY, String());
+ plugin_config.binary = plugin_config.binary_type == PluginConfigAndroid::BINARY_TYPE_LOCAL ? resolve_local_dependency_path(config_base_dir, binary_path) : binary_path;
+
+ if (config_file->has_section(PluginConfigAndroid::DEPENDENCIES_SECTION)) {
+ Vector<String> local_dependencies_paths = config_file->get_value(PluginConfigAndroid::DEPENDENCIES_SECTION, PluginConfigAndroid::DEPENDENCIES_LOCAL_KEY, Vector<String>());
+ if (!local_dependencies_paths.is_empty()) {
+ for (int i = 0; i < local_dependencies_paths.size(); i++) {
+ plugin_config.local_dependencies.push_back(resolve_local_dependency_path(config_base_dir, local_dependencies_paths[i]));
+ }
+ }
+
+ plugin_config.remote_dependencies = config_file->get_value(PluginConfigAndroid::DEPENDENCIES_SECTION, PluginConfigAndroid::DEPENDENCIES_REMOTE_KEY, Vector<String>());
+ plugin_config.custom_maven_repos = config_file->get_value(PluginConfigAndroid::DEPENDENCIES_SECTION, PluginConfigAndroid::DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY, Vector<String>());
+ }
+
+ plugin_config.valid_config = is_plugin_config_valid(plugin_config);
+ plugin_config.last_updated = get_plugin_modification_time(plugin_config, path);
+ }
+ }
+
+ return plugin_config;
+}
+
+String PluginConfigAndroid::get_plugins_binaries(String binary_type, Vector<PluginConfigAndroid> plugins_configs) {
+ String plugins_binaries;
+ if (!plugins_configs.is_empty()) {
+ Vector<String> binaries;
+ for (int i = 0; i < plugins_configs.size(); i++) {
+ PluginConfigAndroid config = plugins_configs[i];
+ if (!config.valid_config) {
+ continue;
+ }
+
+ if (config.binary_type == binary_type) {
+ binaries.push_back(config.binary);
+ }
+
+ if (binary_type == PluginConfigAndroid::BINARY_TYPE_LOCAL) {
+ binaries.append_array(config.local_dependencies);
+ }
+
+ if (binary_type == PluginConfigAndroid::BINARY_TYPE_REMOTE) {
+ binaries.append_array(config.remote_dependencies);
+ }
+ }
+
+ plugins_binaries = String(PluginConfigAndroid::PLUGIN_VALUE_SEPARATOR).join(binaries);
+ }
+
+ return plugins_binaries;
+}
+
+String PluginConfigAndroid::get_plugins_custom_maven_repos(Vector<PluginConfigAndroid> plugins_configs) {
+ String custom_maven_repos;
+ if (!plugins_configs.is_empty()) {
+ Vector<String> repos_urls;
+ for (int i = 0; i < plugins_configs.size(); i++) {
+ PluginConfigAndroid config = plugins_configs[i];
+ if (!config.valid_config) {
+ continue;
+ }
+
+ repos_urls.append_array(config.custom_maven_repos);
+ }
+
+ custom_maven_repos = String(PluginConfigAndroid::PLUGIN_VALUE_SEPARATOR).join(repos_urls);
+ }
+ return custom_maven_repos;
+}
+
+String PluginConfigAndroid::get_plugins_names(Vector<PluginConfigAndroid> plugins_configs) {
+ String plugins_names;
+ if (!plugins_configs.is_empty()) {
+ Vector<String> names;
+ for (int i = 0; i < plugins_configs.size(); i++) {
+ PluginConfigAndroid config = plugins_configs[i];
+ if (!config.valid_config) {
+ continue;
+ }
+
+ names.push_back(config.name);
+ }
+ plugins_names = String(PluginConfigAndroid::PLUGIN_VALUE_SEPARATOR).join(names);
+ }
+
+ return plugins_names;
+}
diff --git a/platform/android/export/godot_plugin_config.h b/platform/android/export/godot_plugin_config.h
new file mode 100644
index 0000000000..c016634371
--- /dev/null
+++ b/platform/android/export/godot_plugin_config.h
@@ -0,0 +1,106 @@
+/*************************************************************************/
+/* godot_plugin_config.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef ANDROID_GODOT_PLUGIN_CONFIG_H
+#define ANDROID_GODOT_PLUGIN_CONFIG_H
+
+#include "core/config/project_settings.h"
+#include "core/error/error_list.h"
+#include "core/io/config_file.h"
+#include "core/string/ustring.h"
+
+/*
+ The `config` section and fields are required and defined as follow:
+- **name**: name of the plugin.
+- **binary_type**: can be either `local` or `remote`. The type affects the **binary** field.
+- **binary**:
+ - if **binary_type** is `local`, then this should be the filename of the plugin `aar` file in the `res://android/plugins` directory (e.g: `MyPlugin.aar`).
+ - if **binary_type** is `remote`, then this should be a declaration for a remote gradle binary (e.g: "org.godot.example:my-plugin:0.0.0").
+
+The `dependencies` section and fields are optional and defined as follow:
+- **local**: contains a list of local `.aar` binary files the plugin depends on. The local binary dependencies must also be located in the `res://android/plugins` directory.
+- **remote**: contains a list of remote binary gradle dependencies for the plugin.
+- **custom_maven_repos**: contains a list of urls specifying custom maven repos required for the plugin's dependencies.
+
+ See https://github.com/godotengine/godot/issues/38157#issuecomment-618773871
+ */
+struct PluginConfigAndroid {
+ inline static const char *PLUGIN_CONFIG_EXT = ".gdap";
+
+ inline static const char *CONFIG_SECTION = "config";
+ inline static const char *CONFIG_NAME_KEY = "name";
+ inline static const char *CONFIG_BINARY_TYPE_KEY = "binary_type";
+ inline static const char *CONFIG_BINARY_KEY = "binary";
+
+ inline static const char *DEPENDENCIES_SECTION = "dependencies";
+ inline static const char *DEPENDENCIES_LOCAL_KEY = "local";
+ inline static const char *DEPENDENCIES_REMOTE_KEY = "remote";
+ inline static const char *DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY = "custom_maven_repos";
+
+ inline static const char *BINARY_TYPE_LOCAL = "local";
+ inline static const char *BINARY_TYPE_REMOTE = "remote";
+
+ inline static const char *PLUGIN_VALUE_SEPARATOR = "|";
+
+ // Set to true when the config file is properly loaded.
+ bool valid_config = false;
+ // Unix timestamp of last change to this plugin.
+ uint64_t last_updated = 0;
+
+ // Required config section
+ String name;
+ String binary_type;
+ String binary;
+
+ // Optional dependencies section
+ Vector<String> local_dependencies;
+ Vector<String> remote_dependencies;
+ Vector<String> custom_maven_repos;
+
+ static String resolve_local_dependency_path(String plugin_config_dir, String dependency_path);
+
+ static PluginConfigAndroid resolve_prebuilt_plugin(PluginConfigAndroid prebuilt_plugin, String plugin_config_dir);
+
+ static Vector<PluginConfigAndroid> get_prebuilt_plugins(String plugins_base_dir);
+
+ static bool is_plugin_config_valid(PluginConfigAndroid plugin_config);
+
+ static uint64_t get_plugin_modification_time(const PluginConfigAndroid &plugin_config, const String &config_path);
+
+ static PluginConfigAndroid load_plugin_config(Ref<ConfigFile> config_file, const String &path);
+
+ static String get_plugins_binaries(String binary_type, Vector<PluginConfigAndroid> plugins_configs);
+
+ static String get_plugins_custom_maven_repos(Vector<PluginConfigAndroid> plugins_configs);
+
+ static String get_plugins_names(Vector<PluginConfigAndroid> plugins_configs);
+};
+
+#endif // GODOT_PLUGIN_CONFIG_H
diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp
new file mode 100644
index 0000000000..b0d623827e
--- /dev/null
+++ b/platform/android/export/gradle_export_util.cpp
@@ -0,0 +1,276 @@
+/*************************************************************************/
+/* gradle_export_util.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gradle_export_util.h"
+
+int _get_android_orientation_value(DisplayServer::ScreenOrientation screen_orientation) {
+ switch (screen_orientation) {
+ case DisplayServer::SCREEN_PORTRAIT:
+ return 1;
+ case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
+ return 8;
+ case DisplayServer::SCREEN_REVERSE_PORTRAIT:
+ return 9;
+ case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
+ return 11;
+ case DisplayServer::SCREEN_SENSOR_PORTRAIT:
+ return 12;
+ case DisplayServer::SCREEN_SENSOR:
+ return 13;
+ case DisplayServer::SCREEN_LANDSCAPE:
+ default:
+ return 0;
+ }
+}
+
+String _get_android_orientation_label(DisplayServer::ScreenOrientation screen_orientation) {
+ switch (screen_orientation) {
+ case DisplayServer::SCREEN_PORTRAIT:
+ return "portrait";
+ case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
+ return "reverseLandscape";
+ case DisplayServer::SCREEN_REVERSE_PORTRAIT:
+ return "reversePortrait";
+ case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
+ return "userLandscape";
+ case DisplayServer::SCREEN_SENSOR_PORTRAIT:
+ return "userPortrait";
+ case DisplayServer::SCREEN_SENSOR:
+ return "fullUser";
+ case DisplayServer::SCREEN_LANDSCAPE:
+ default:
+ return "landscape";
+ }
+}
+
+// Utility method used to create a directory.
+Error create_directory(const String &p_dir) {
+ if (!DirAccess::exists(p_dir)) {
+ DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'.");
+ Error err = filesystem_da->make_dir_recursive(p_dir);
+ ERR_FAIL_COND_V_MSG(err, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'.");
+ memdelete(filesystem_da);
+ }
+ return OK;
+}
+
+// Writes p_data into a file at p_path, creating directories if necessary.
+// Note: this will overwrite the file at p_path if it already exists.
+Error store_file_at_path(const String &p_path, const Vector<uint8_t> &p_data) {
+ String dir = p_path.get_base_dir();
+ Error err = create_directory(dir);
+ if (err != OK) {
+ return err;
+ }
+ FileAccess *fa = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
+ fa->store_buffer(p_data.ptr(), p_data.size());
+ memdelete(fa);
+ return OK;
+}
+
+// Writes string p_data into a file at p_path, creating directories if necessary.
+// Note: this will overwrite the file at p_path if it already exists.
+Error store_string_at_path(const String &p_path, const String &p_data) {
+ String dir = p_path.get_base_dir();
+ Error err = create_directory(dir);
+ if (err != OK) {
+ if (OS::get_singleton()->is_stdout_verbose()) {
+ print_error("Unable to write data into " + p_path);
+ }
+ return err;
+ }
+ FileAccess *fa = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
+ fa->store_string(p_data);
+ memdelete(fa);
+ return OK;
+}
+
+// Implementation of EditorExportSaveFunction.
+// This method will only be called as an input to export_project_files.
+// It is used by the export_project_files method to save all the asset files into the gradle project.
+// It's functionality mirrors that of the method save_apk_file.
+// This method will be called ONLY when custom build is enabled.
+Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
+ CustomExportData *export_data = (CustomExportData *)p_userdata;
+ String dst_path = p_path.replace_first("res://", export_data->assets_directory + "/");
+ print_verbose("Saving project files from " + p_path + " into " + dst_path);
+ Error err = store_file_at_path(dst_path, p_data);
+ return err;
+}
+
+String _android_xml_escape(const String &p_string) {
+ // Android XML requires strings to be both valid XML (`xml_escape()`) but also
+ // to escape characters which are valid XML but have special meaning in Android XML.
+ // https://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling
+ // Note: Didn't handle U+XXXX unicode chars, could be done if needed.
+ return p_string
+ .replace("@", "\\@")
+ .replace("?", "\\?")
+ .replace("'", "\\'")
+ .replace("\"", "\\\"")
+ .replace("\n", "\\n")
+ .replace("\t", "\\t")
+ .xml_escape(false);
+}
+
+// Creates strings.xml files inside the gradle project for different locales.
+Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name) {
+ print_verbose("Creating strings resources for supported locales for project " + project_name);
+ // Stores the string into the default values directory.
+ String processed_default_xml_string = vformat(godot_project_name_xml_string, _android_xml_escape(project_name));
+ store_string_at_path("res://android/build/res/values/godot_project_name_string.xml", processed_default_xml_string);
+
+ // Searches the Gradle project res/ directory to find all supported locales
+ DirAccessRef da = DirAccess::open("res://android/build/res");
+ if (!da) {
+ if (OS::get_singleton()->is_stdout_verbose()) {
+ print_error("Unable to open Android resources directory.");
+ }
+ return ERR_CANT_OPEN;
+ }
+ da->list_dir_begin();
+ while (true) {
+ String file = da->get_next();
+ if (file == "") {
+ break;
+ }
+ if (!file.begins_with("values-")) {
+ // NOTE: This assumes all directories that start with "values-" are for localization.
+ continue;
+ }
+ String locale = file.replace("values-", "").replace("-r", "_");
+ String property_name = "application/config/name_" + locale;
+ String locale_directory = "res://android/build/res/" + file + "/godot_project_name_string.xml";
+ if (ProjectSettings::get_singleton()->has_setting(property_name)) {
+ String locale_project_name = ProjectSettings::get_singleton()->get(property_name);
+ String processed_xml_string = vformat(godot_project_name_xml_string, _android_xml_escape(locale_project_name));
+ print_verbose("Storing project name for locale " + locale + " under " + locale_directory);
+ store_string_at_path(locale_directory, processed_xml_string);
+ } else {
+ // TODO: Once the legacy build system is deprecated we don't need to have xml files for this else branch
+ store_string_at_path(locale_directory, processed_default_xml_string);
+ }
+ }
+ da->list_dir_end();
+ return OK;
+}
+
+String bool_to_string(bool v) {
+ return v ? "true" : "false";
+}
+
+String _get_gles_tag() {
+ bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/driver/driver_name") == "GLES3" &&
+ !ProjectSettings::get_singleton()->get("rendering/driver/fallback_to_gles2");
+ return min_gles3 ? " <uses-feature android:glEsVersion=\"0x00030000\" android:required=\"true\" />\n" : "";
+}
+
+String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset) {
+ String manifest_screen_sizes = " <supports-screens \n tools:node=\"replace\"";
+ String sizes[] = { "small", "normal", "large", "xlarge" };
+ size_t num_sizes = sizeof(sizes) / sizeof(sizes[0]);
+ for (size_t i = 0; i < num_sizes; i++) {
+ String feature_name = vformat("screen/support_%s", sizes[i]);
+ String feature_support = bool_to_string(p_preset->get(feature_name));
+ String xml_entry = vformat("\n android:%sScreens=\"%s\"", sizes[i], feature_support);
+ manifest_screen_sizes += xml_entry;
+ }
+ manifest_screen_sizes += " />\n";
+ return manifest_screen_sizes;
+}
+
+String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
+ String manifest_xr_features;
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ if (uses_xr) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n";
+
+ int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
+ if (hand_tracking_index == 1) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n";
+ } else if (hand_tracking_index == 2) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"true\" />\n";
+ }
+ }
+ return manifest_xr_features;
+}
+
+String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) {
+ String package_name = p_preset->get("package/unique_name");
+ String manifest_instrumentation_text = vformat(
+ " <instrumentation\n"
+ " tools:node=\"replace\"\n"
+ " android:name=\".GodotInstrumentation\"\n"
+ " android:icon=\"@mipmap/icon\"\n"
+ " android:label=\"@string/godot_project_name_string\"\n"
+ " android:targetPackage=\"%s\" />\n",
+ package_name);
+ return manifest_instrumentation_text;
+}
+
+String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ String orientation = _get_android_orientation_label(DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation"))));
+ String manifest_activity_text = vformat(
+ " <activity android:name=\"com.godot.game.GodotApp\" "
+ "tools:replace=\"android:screenOrientation\" "
+ "android:screenOrientation=\"%s\">\n",
+ orientation);
+ if (uses_xr) {
+ manifest_activity_text += " <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"true\" />\n";
+ } else {
+ manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n";
+ }
+ manifest_activity_text += " </activity>\n";
+ return manifest_activity_text;
+}
+
+String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission) {
+ String manifest_application_text = vformat(
+ " <application android:label=\"@string/godot_project_name_string\"\n"
+ " android:allowBackup=\"%s\"\n"
+ " android:icon=\"@mipmap/icon\"\n"
+ " android:isGame=\"%s\"\n"
+ " android:hasFragileUserData=\"%s\"\n"
+ " android:requestLegacyExternalStorage=\"%s\"\n"
+ " tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData,android:requestLegacyExternalStorage\"\n"
+ " tools:ignore=\"GoogleAppIndexingWarning\">\n\n",
+ bool_to_string(p_preset->get("user_data_backup/allow")),
+ bool_to_string(p_preset->get("package/classify_as_game")),
+ bool_to_string(p_preset->get("package/retain_data_on_uninstall")),
+ bool_to_string(p_has_storage_permission));
+
+ manifest_application_text += _get_activity_tag(p_preset);
+ manifest_application_text += " </application>\n";
+ return manifest_application_text;
+}
diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h
index 95f870bc35..744022f1f9 100644
--- a/platform/android/export/gradle_export_util.h
+++ b/platform/android/export/gradle_export_util.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,9 +31,9 @@
#ifndef GODOT_GRADLE_EXPORT_UTIL_H
#define GODOT_GRADLE_EXPORT_UTIL_H
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
#include "core/io/zip_io.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
#include "core/os/os.h"
#include "editor/editor_export.h"
@@ -44,202 +44,49 @@ const String godot_project_name_xml_string = R"(<?xml version="1.0" encoding="ut
</resources>
)";
+struct CustomExportData {
+ String assets_directory;
+ bool debug;
+ Vector<String> libs;
+};
+
+int _get_android_orientation_value(DisplayServer::ScreenOrientation screen_orientation);
+
+String _get_android_orientation_label(DisplayServer::ScreenOrientation screen_orientation);
+
// Utility method used to create a directory.
-Error create_directory(const String &p_dir) {
- if (!DirAccess::exists(p_dir)) {
- DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'.");
- Error err = filesystem_da->make_dir_recursive(p_dir);
- ERR_FAIL_COND_V_MSG(err, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'.");
- memdelete(filesystem_da);
- }
- return OK;
-}
-
-// Implementation of EditorExportSaveSharedObject.
-// This method will only be called as an input to export_project_files.
-// This method lets the .so files for all ABIs to be copied
-// into the gradle project from the .AAR file
-Error ignore_so_file(void *p_userdata, const SharedObject &p_so) {
- return OK;
-}
+Error create_directory(const String &p_dir);
// Writes p_data into a file at p_path, creating directories if necessary.
// Note: this will overwrite the file at p_path if it already exists.
-Error store_file_at_path(const String &p_path, const Vector<uint8_t> &p_data) {
- String dir = p_path.get_base_dir();
- Error err = create_directory(dir);
- if (err != OK) {
- return err;
- }
- FileAccess *fa = FileAccess::open(p_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
- fa->store_buffer(p_data.ptr(), p_data.size());
- memdelete(fa);
- return OK;
-}
+Error store_file_at_path(const String &p_path, const Vector<uint8_t> &p_data);
// Writes string p_data into a file at p_path, creating directories if necessary.
// Note: this will overwrite the file at p_path if it already exists.
-Error store_string_at_path(const String &p_path, const String &p_data) {
- String dir = p_path.get_base_dir();
- Error err = create_directory(dir);
- if (err != OK) {
- return err;
- }
- FileAccess *fa = FileAccess::open(p_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
- fa->store_string(p_data);
- memdelete(fa);
- return OK;
-}
+Error store_string_at_path(const String &p_path, const String &p_data);
// Implementation of EditorExportSaveFunction.
// This method will only be called as an input to export_project_files.
// It is used by the export_project_files method to save all the asset files into the gradle project.
// It's functionality mirrors that of the method save_apk_file.
// This method will be called ONLY when custom build is enabled.
-Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
- String dst_path = p_path.replace_first("res://", "res://android/build/assets/");
- Error err = store_file_at_path(dst_path, p_data);
- return err;
-}
+Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
// Creates strings.xml files inside the gradle project for different locales.
-Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name) {
- // Stores the string into the default values directory.
- String processed_default_xml_string = vformat(godot_project_name_xml_string, project_name.xml_escape(true));
- store_string_at_path("res://android/build/res/values/godot_project_name_string.xml", processed_default_xml_string);
-
- // Searches the Gradle project res/ directory to find all supported locales
- DirAccessRef da = DirAccess::open("res://android/build/res");
- if (!da) {
- return ERR_CANT_OPEN;
- }
- da->list_dir_begin();
- while (true) {
- String file = da->get_next();
- if (file == "") {
- break;
- }
- if (!file.begins_with("values-")) {
- // NOTE: This assumes all directories that start with "values-" are for localization.
- continue;
- }
- String locale = file.replace("values-", "").replace("-r", "_");
- String property_name = "application/config/name_" + locale;
- String locale_directory = "res://android/build/res/" + file + "/godot_project_name_string.xml";
- if (ProjectSettings::get_singleton()->has_setting(property_name)) {
- String locale_project_name = ProjectSettings::get_singleton()->get(property_name);
- String processed_xml_string = vformat(godot_project_name_xml_string, locale_project_name.xml_escape(true));
- store_string_at_path(locale_directory, processed_xml_string);
- } else {
- // TODO: Once the legacy build system is deprecated we don't need to have xml files for this else branch
- store_string_at_path(locale_directory, processed_default_xml_string);
- }
- }
- da->list_dir_end();
- return OK;
-}
-
-String bool_to_string(bool v) {
- return v ? "true" : "false";
-}
-
-String _get_gles_tag() {
- bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name") == "GLES3" &&
- !ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2");
- return min_gles3 ? " <uses-feature android:glEsVersion=\"0x00030000\" android:required=\"true\" />\n" : "";
-}
-
-String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset) {
- String manifest_screen_sizes = " <supports-screens \n tools:node=\"replace\"";
- String sizes[] = { "small", "normal", "large", "xlarge" };
- size_t num_sizes = sizeof(sizes) / sizeof(sizes[0]);
- for (size_t i = 0; i < num_sizes; i++) {
- String feature_name = vformat("screen/support_%s", sizes[i]);
- String feature_support = bool_to_string(p_preset->get(feature_name));
- String xml_entry = vformat("\n android:%sScreens=\"%s\"", sizes[i], feature_support);
- manifest_screen_sizes += xml_entry;
- }
- manifest_screen_sizes += " />\n";
- return manifest_screen_sizes;
-}
-
-String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
- String manifest_xr_features;
- bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
- if (uses_xr) {
- int dof_index = p_preset->get("xr_features/degrees_of_freedom"); // 0: none, 1: 3dof and 6dof, 2: 6dof
- if (dof_index == 1) {
- manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"false\" android:version=\"1\" />\n";
- } else if (dof_index == 2) {
- manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n";
- }
- int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
- if (hand_tracking_index == 1) {
- manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n";
- } else if (hand_tracking_index == 2) {
- manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"true\" />\n";
- }
- }
- return manifest_xr_features;
-}
-
-String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) {
- String package_name = p_preset->get("package/unique_name");
- String manifest_instrumentation_text = vformat(
- " <instrumentation\n"
- " tools:node=\"replace\"\n"
- " android:name=\".GodotInstrumentation\"\n"
- " android:icon=\"@mipmap/icon\"\n"
- " android:label=\"@string/godot_project_name_string\"\n"
- " android:targetPackage=\"%s\" />\n",
- package_name);
- return manifest_instrumentation_text;
-}
-
-String _get_plugins_tag(const String &plugins_names) {
- if (!plugins_names.empty()) {
- return vformat(" <meta-data tools:node=\"replace\" android:name=\"plugins\" android:value=\"%s\" />\n", plugins_names);
- } else {
- return " <meta-data tools:node=\"remove\" android:name=\"plugins\" />\n";
- }
-}
-
-String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
- bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
- String orientation = (int)(p_preset->get("screen/orientation")) == 1 ? "portrait" : "landscape";
- String manifest_activity_text = vformat(
- " <activity android:name=\"com.godot.game.GodotApp\" "
- "tools:replace=\"android:screenOrientation\" "
- "android:screenOrientation=\"%s\">\n",
- orientation);
- if (uses_xr) {
- String focus_awareness = bool_to_string(p_preset->get("xr_features/focus_awareness"));
- manifest_activity_text += vformat(" <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"%s\" />\n", focus_awareness);
- } else {
- manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n";
- }
- manifest_activity_text += " </activity>\n";
- return manifest_activity_text;
-}
-
-String _get_application_tag(const Ref<EditorExportPreset> &p_preset, const String &plugins_names) {
- bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
- String manifest_application_text =
- " <application android:label=\"@string/godot_project_name_string\"\n"
- " android:allowBackup=\"false\" tools:ignore=\"GoogleAppIndexingWarning\"\n"
- " android:icon=\"@mipmap/icon\">)\n\n"
- " <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n";
-
- manifest_application_text += _get_plugins_tag(plugins_names);
- if (uses_xr) {
- manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n";
- }
- manifest_application_text += _get_activity_tag(p_preset);
- manifest_application_text += " </application>\n";
- return manifest_application_text;
-}
+Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name);
+
+String bool_to_string(bool v);
+
+String _get_gles_tag();
+
+String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset);
+
+String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset);
+
+String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset);
+
+String _get_activity_tag(const Ref<EditorExportPreset> &p_preset);
+
+String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission);
#endif //GODOT_GRADLE_EXPORT_UTIL_H
diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp
index 05d5fb576d..90370878b7 100644
--- a/platform/android/file_access_android.cpp
+++ b/platform/android/file_access_android.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -29,12 +29,11 @@
/*************************************************************************/
#include "file_access_android.h"
-#include "core/print_string.h"
+#include "core/string/print_string.h"
AAssetManager *FileAccessAndroid::asset_manager = nullptr;
/*void FileAccessAndroid::make_default() {
-
create_func=create_android;
}*/
@@ -72,8 +71,9 @@ bool FileAccessAndroid::is_open() const {
return a != nullptr;
}
-void FileAccessAndroid::seek(size_t p_position) {
+void FileAccessAndroid::seek(uint64_t p_position) {
ERR_FAIL_COND(!a);
+
AAsset_seek(a, p_position, SEEK_SET);
pos = p_position;
if (pos > len) {
@@ -90,11 +90,11 @@ void FileAccessAndroid::seek_end(int64_t p_position) {
pos = len + p_position;
}
-size_t FileAccessAndroid::get_position() const {
+uint64_t FileAccessAndroid::get_position() const {
return pos;
}
-size_t FileAccessAndroid::get_len() const {
+uint64_t FileAccessAndroid::get_length() const {
return len;
}
@@ -114,8 +114,10 @@ uint8_t FileAccessAndroid::get_8() const {
return byte;
}
-int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const {
- off_t r = AAsset_read(a, p_dst, p_length);
+uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
+ ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
+
+ int r = AAsset_read(a, p_dst, p_length);
if (pos + p_length > len) {
eof = true;
@@ -158,11 +160,6 @@ bool FileAccessAndroid::file_exists(const String &p_path) {
return true;
}
-FileAccessAndroid::FileAccessAndroid() {
- a = nullptr;
- eof = false;
-}
-
FileAccessAndroid::~FileAccessAndroid() {
close();
}
diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h
index a347c63ffb..bb4ce36947 100644
--- a/platform/android/file_access_android.h
+++ b/platform/android/file_access_android.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,7 +31,7 @@
#ifndef FILE_ACCESS_ANDROID_H
#define FILE_ACCESS_ANDROID_H
-#include "core/os/file_access.h"
+#include "core/io/file_access.h"
#include <android/asset_manager.h>
#include <android/log.h>
#include <stdio.h>
@@ -39,10 +39,10 @@
class FileAccessAndroid : public FileAccess {
static FileAccess *create_android();
- mutable AAsset *a;
- mutable size_t len;
- mutable size_t pos;
- mutable bool eof;
+ mutable AAsset *a = nullptr;
+ mutable uint64_t len = 0;
+ mutable uint64_t pos = 0;
+ mutable bool eof = false;
public:
static AAssetManager *asset_manager;
@@ -51,15 +51,15 @@ public:
virtual void close(); ///< close a file
virtual bool is_open() const; ///< true when file is open
- virtual void seek(size_t p_position); ///< seek to a given position
+ virtual void seek(uint64_t p_position); ///< seek to a given position
virtual void seek_end(int64_t p_position = 0); ///< seek from the end of file
- virtual size_t get_position() const; ///< get position in the file
- virtual size_t get_len() const; ///< get size of the file
+ virtual uint64_t get_position() const; ///< get position in the file
+ virtual uint64_t get_length() const; ///< get size of the file
virtual bool eof_reached() const; ///< reading passed EOF
virtual uint8_t get_8() const; ///< get a byte
- virtual int get_buffer(uint8_t *p_dst, int p_length) const;
+ virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const;
virtual Error get_error() const; ///< get last error
@@ -74,7 +74,6 @@ public:
//static void make_default();
- FileAccessAndroid();
~FileAccessAndroid();
};
diff --git a/platform/android/file_access_jandroid.cpp b/platform/android/file_access_jandroid.cpp
deleted file mode 100644
index df8b57fd3a..0000000000
--- a/platform/android/file_access_jandroid.cpp
+++ /dev/null
@@ -1,197 +0,0 @@
-/*************************************************************************/
-/* file_access_jandroid.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "file_access_jandroid.h"
-#include "core/os/os.h"
-#include "thread_jandroid.h"
-#include <unistd.h>
-
-jobject FileAccessJAndroid::io = nullptr;
-jclass FileAccessJAndroid::cls;
-jmethodID FileAccessJAndroid::_file_open = 0;
-jmethodID FileAccessJAndroid::_file_get_size = 0;
-jmethodID FileAccessJAndroid::_file_seek = 0;
-jmethodID FileAccessJAndroid::_file_read = 0;
-jmethodID FileAccessJAndroid::_file_tell = 0;
-jmethodID FileAccessJAndroid::_file_eof = 0;
-jmethodID FileAccessJAndroid::_file_close = 0;
-
-FileAccess *FileAccessJAndroid::create_jandroid() {
- return memnew(FileAccessJAndroid);
-}
-
-Error FileAccessJAndroid::_open(const String &p_path, int p_mode_flags) {
- if (is_open())
- close();
-
- String path = fix_path(p_path).simplify_path();
- if (path.begins_with("/"))
- path = path.substr(1, path.length());
- else if (path.begins_with("res://"))
- path = path.substr(6, path.length());
-
- JNIEnv *env = ThreadAndroid::get_env();
-
- jstring js = env->NewStringUTF(path.utf8().get_data());
- int res = env->CallIntMethod(io, _file_open, js, (p_mode_flags & WRITE) ? true : false);
- env->DeleteLocalRef(js);
-
- OS::get_singleton()->print("fopen: '%s' ret %i\n", path.utf8().get_data(), res);
-
- if (res <= 0)
- return ERR_FILE_CANT_OPEN;
- id = res;
-
- return OK;
-}
-
-void FileAccessJAndroid::close() {
- if (!is_open())
- return;
-
- JNIEnv *env = ThreadAndroid::get_env();
-
- env->CallVoidMethod(io, _file_close, id);
- id = 0;
-}
-
-bool FileAccessJAndroid::is_open() const {
- return id != 0;
-}
-
-void FileAccessJAndroid::seek(size_t p_position) {
- JNIEnv *env = ThreadAndroid::get_env();
-
- ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
- env->CallVoidMethod(io, _file_seek, id, p_position);
-}
-
-void FileAccessJAndroid::seek_end(int64_t p_position) {
- ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
-
- seek(get_len());
-}
-
-size_t FileAccessJAndroid::get_position() const {
- JNIEnv *env = ThreadAndroid::get_env();
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- return env->CallIntMethod(io, _file_tell, id);
-}
-
-size_t FileAccessJAndroid::get_len() const {
- JNIEnv *env = ThreadAndroid::get_env();
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- return env->CallIntMethod(io, _file_get_size, id);
-}
-
-bool FileAccessJAndroid::eof_reached() const {
- JNIEnv *env = ThreadAndroid::get_env();
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- return env->CallIntMethod(io, _file_eof, id);
-}
-
-uint8_t FileAccessJAndroid::get_8() const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- uint8_t byte;
- get_buffer(&byte, 1);
- return byte;
-}
-
-int FileAccessJAndroid::get_buffer(uint8_t *p_dst, int p_length) const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- if (p_length == 0)
- return 0;
- JNIEnv *env = ThreadAndroid::get_env();
-
- jbyteArray jca = (jbyteArray)env->CallObjectMethod(io, _file_read, id, p_length);
-
- int len = env->GetArrayLength(jca);
- env->GetByteArrayRegion(jca, 0, len, (jbyte *)p_dst);
- env->DeleteLocalRef((jobject)jca);
-
- return len;
-}
-
-Error FileAccessJAndroid::get_error() const {
- if (eof_reached())
- return ERR_FILE_EOF;
- return OK;
-}
-
-void FileAccessJAndroid::flush() {
-}
-
-void FileAccessJAndroid::store_8(uint8_t p_dest) {
-}
-
-bool FileAccessJAndroid::file_exists(const String &p_path) {
- JNIEnv *env = ThreadAndroid::get_env();
-
- String path = fix_path(p_path).simplify_path();
- if (path.begins_with("/"))
- path = path.substr(1, path.length());
- else if (path.begins_with("res://"))
- path = path.substr(6, path.length());
-
- jstring js = env->NewStringUTF(path.utf8().get_data());
- int res = env->CallIntMethod(io, _file_open, js, false);
- if (res <= 0) {
- env->DeleteLocalRef(js);
- return false;
- }
- env->CallVoidMethod(io, _file_close, res);
- env->DeleteLocalRef(js);
- return true;
-}
-
-void FileAccessJAndroid::setup(jobject p_io) {
- io = p_io;
- JNIEnv *env = ThreadAndroid::get_env();
-
- jclass c = env->GetObjectClass(io);
- cls = (jclass)env->NewGlobalRef(c);
-
- _file_open = env->GetMethodID(cls, "file_open", "(Ljava/lang/String;Z)I");
- _file_get_size = env->GetMethodID(cls, "file_get_size", "(I)I");
- _file_tell = env->GetMethodID(cls, "file_tell", "(I)I");
- _file_eof = env->GetMethodID(cls, "file_eof", "(I)Z");
- _file_seek = env->GetMethodID(cls, "file_seek", "(II)V");
- _file_read = env->GetMethodID(cls, "file_read", "(II)[B");
- _file_close = env->GetMethodID(cls, "file_close", "(I)V");
-}
-
-FileAccessJAndroid::FileAccessJAndroid() {
- id = 0;
-}
-
-FileAccessJAndroid::~FileAccessJAndroid() {
- if (is_open())
- close();
-}
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index e94681659c..d7bf6cef30 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -19,21 +19,19 @@
<application
android:label="@string/godot_project_name_string"
android:allowBackup="false"
- tools:ignore="GoogleAppIndexingWarning"
- android:icon="@mipmap/icon" >
+ android:icon="@mipmap/icon"
+ android:isGame="true"
+ android:hasFragileUserData="false"
+ android:requestLegacyExternalStorage="false"
+ tools:ignore="GoogleAppIndexingWarning" >
- <!-- The following metadata values are replaced when Godot exports, modifying them here has no effect. -->
- <!-- Do these changes in the export preset. Adding new ones is fine. -->
-
- <!-- XR mode metadata. This is modified by the exporter based on the selected xr mode. DO NOT CHANGE the values here. -->
+ <!-- Records the version of the Godot editor used for building -->
<meta-data
- android:name="xr_mode_metadata_name"
- android:value="xr_mode_metadata_value" />
+ android:name="org.godotengine.editor.version"
+ android:value="${godotEditorVersion}" />
- <!-- Metadata populated at export time and used by Godot to figure out which plugins must be enabled. -->
- <meta-data
- android:name="plugins"
- android:value="plugins_value"/>
+ <!-- The following metadata values are replaced when Godot exports, modifying them here has no effect. -->
+ <!-- Do these changes in the export preset. Adding new ones is fine. -->
<activity
android:name=".GodotApp"
@@ -45,12 +43,16 @@
android:resizeableActivity="false"
tools:ignore="UnusedAttribute" >
- <!-- Focus awareness metadata is updated at export time if the user enables it in the 'Xr Features' section. -->
- <meta-data android:name="com.oculus.vr.focusaware" android:value="false" />
+ <!-- Focus awareness metadata is removed at export time if the xr mode is not VR. -->
+ <meta-data android:name="com.oculus.vr.focusaware" android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
+
+ <!-- Enable access to OpenXR on Oculus mobile devices, no-op on other Android
+ platforms. -->
+ <category android:name="com.oculus.intent.category.VR" />
</intent-filter>
</activity>
diff --git a/platform/android/java/app/assetPacks/installTime/build.gradle b/platform/android/java/app/assetPacks/installTime/build.gradle
new file mode 100644
index 0000000000..b06faac374
--- /dev/null
+++ b/platform/android/java/app/assetPacks/installTime/build.gradle
@@ -0,0 +1,8 @@
+apply plugin: 'com.android.asset-pack'
+
+assetPack {
+ packName = "installTime" // Directory name for the asset pack
+ dynamicDelivery {
+ deliveryType = "install-time" // Delivery mode
+ }
+}
diff --git a/platform/android/java/app/assets/.gitignore b/platform/android/java/app/assets/.gitignore
new file mode 100644
index 0000000000..d6b7ef32c8
--- /dev/null
+++ b/platform/android/java/app/assets/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index ceacfec9e1..a391a3ca9a 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -6,7 +6,7 @@ buildscript {
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
classpath libraries.androidGradlePlugin
@@ -18,9 +18,8 @@ apply plugin: 'com.android.application'
allprojects {
repositories {
- mavenCentral()
google()
- jcenter()
+ mavenCentral()
// Godot user plugins custom maven repos
String[] mavenRepos = getGodotPluginsMavenRepos()
@@ -35,9 +34,8 @@ allprojects {
}
dependencies {
- implementation libraries.supportCoreUtils
implementation libraries.kotlinStdLib
- implementation libraries.v4Support
+ implementation libraries.androidxFragment
if (rootProject.findProject(":lib")) {
implementation project(":lib")
@@ -70,14 +68,16 @@ android {
buildToolsVersion versions.buildTools
compileOptions {
- sourceCompatibility 1.8
- targetCompatibility 1.8
+ sourceCompatibility versions.javaVersion
+ targetCompatibility versions.javaVersion
}
+ assetPacks = [":assetPacks:installTime"]
+
defaultConfig {
// The default ignore pattern for the 'assets' directory includes hidden files and directories which are used by Godot projects.
aaptOptions {
- ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"
+ ignoreAssetsPattern "!.svn:!.git:!.gitignore:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"
}
ndk {
@@ -85,6 +85,8 @@ android {
abiFilters export_abi_list
}
+ manifestPlaceholders = [godotEditorVersion: getGodotEditorVersion()]
+
// Feel free to modify the application id to your own.
applicationId getExportPackageName()
versionCode getExportVersionCode()
@@ -98,18 +100,62 @@ android {
disable 'MissingTranslation', 'UnusedResources'
}
+ ndkVersion versions.ndkVersion
+
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
- // Should be uncommented for development purpose within Android Studio
- // doNotStrip '**/*.so'
+ // 'doNotStrip' is enabled for development within Android Studio
+ if (shouldNotStrip()) {
+ doNotStrip '**/*.so'
+ }
+ }
+
+ signingConfigs {
+ debug {
+ if (hasCustomDebugKeystore()) {
+ storeFile new File(getDebugKeystoreFile())
+ storePassword getDebugKeystorePassword()
+ keyAlias getDebugKeyAlias()
+ keyPassword getDebugKeystorePassword()
+ }
+ }
+
+ release {
+ File keystoreFile = new File(getReleaseKeystoreFile())
+ if (keystoreFile.isFile()) {
+ storeFile keystoreFile
+ storePassword getReleaseKeystorePassword()
+ keyAlias getReleaseKeyAlias()
+ keyPassword getReleaseKeystorePassword()
+ }
+ }
}
- // Both signing and zip-aligning will be done at export time
- buildTypes.all { buildType ->
- buildType.zipAlignEnabled false
- buildType.signingConfig null
+ buildTypes {
+
+ debug {
+ // Signing and zip-aligning are skipped for prebuilt builds, but
+ // performed for custom builds.
+ zipAlignEnabled shouldZipAlign()
+ if (shouldSign()) {
+ signingConfig signingConfigs.debug
+ } else {
+ signingConfig null
+ }
+ }
+
+ release {
+ // Signing and zip-aligning are skipped for prebuilt builds, but
+ // performed for custom builds.
+ zipAlignEnabled shouldZipAlign()
+ if (shouldSign()) {
+ signingConfig signingConfigs.release
+ } else {
+ signingConfig null
+ }
+ }
}
sourceSets {
@@ -120,7 +166,7 @@ android {
aidl.srcDirs = ['aidl']
assets.srcDirs = ['assets']
}
- debug.jniLibs.srcDirs = ['libs/debug']
+ debug.jniLibs.srcDirs = ['libs/debug', 'libs/debug/vulkan_validation_layers']
release.jniLibs.srcDirs = ['libs/release']
}
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index d1176e6196..2a2850df0f 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -1,21 +1,21 @@
ext.versions = [
- androidGradlePlugin: '3.5.3',
- compileSdk : 29,
- minSdk : 18,
- targetSdk : 29,
- buildTools : '29.0.3',
- supportCoreUtils : '1.0.0',
- kotlinVersion : '1.3.61',
- v4Support : '1.0.0'
+ androidGradlePlugin: '7.0.3',
+ compileSdk : 30,
+ minSdk : 19, // Also update 'platform/android/java/lib/AndroidManifest.xml#minSdkVersion' value
+ targetSdk : 30, // Also update 'platform/android/java/lib/AndroidManifest.xml#targetSdkVersion' value
+ buildTools : '30.0.3',
+ kotlinVersion : '1.5.10',
+ fragmentVersion : '1.3.6',
+ javaVersion : 11,
+ ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated.
]
ext.libraries = [
androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin",
- supportCoreUtils : "androidx.legacy:legacy-support-core-utils:$versions.supportCoreUtils",
kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion",
kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlinVersion",
- v4Support : "androidx.legacy:legacy-support-v4:$versions.v4Support"
+ androidxFragment : "androidx.fragment:fragment:$versions.fragmentVersion",
]
ext.getExportPackageName = { ->
@@ -33,7 +33,11 @@ ext.getExportVersionCode = { ->
if (versionCode == null || versionCode.isEmpty()) {
versionCode = "1"
}
- return Integer.parseInt(versionCode)
+ try {
+ return Integer.parseInt(versionCode)
+ } catch (NumberFormatException ignored) {
+ return 1
+ }
}
ext.getExportVersionName = { ->
@@ -44,7 +48,56 @@ ext.getExportVersionName = { ->
return versionName
}
-final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"
+ext.getGodotEditorVersion = { ->
+ String editorVersion = project.hasProperty("godot_editor_version") ? project.property("godot_editor_version") : ""
+ if (editorVersion == null || editorVersion.isEmpty()) {
+ // Try the library version first
+ editorVersion = getGodotLibraryVersion()
+
+ if (editorVersion.isEmpty()) {
+ // Fallback value.
+ editorVersion = "custom_build"
+ }
+ }
+ return editorVersion
+}
+
+ext.getGodotLibraryVersion = { ->
+ // Attempt to read the version from the `version.py` file.
+ String libraryVersion = ""
+
+ File versionFile = new File("../../../version.py")
+ if (versionFile.isFile()) {
+ List<String> requiredKeys = ["major", "minor", "patch", "status", "module_config"]
+ def map = [:]
+
+ List<String> lines = versionFile.readLines()
+ for (String line in lines) {
+ String[] keyValue = line.split("=")
+ String key = keyValue[0].trim()
+ String value = keyValue[1].trim().replaceAll("\"", "")
+
+ if (requiredKeys.contains(key)) {
+ if (!value.isEmpty()) {
+ map[key] = value
+ }
+ requiredKeys.remove(key)
+ }
+ }
+
+ if (requiredKeys.empty) {
+ libraryVersion = map.values().join(".")
+ }
+ }
+
+ if (libraryVersion.isEmpty()) {
+ // Fallback value in case we're unable to read the file.
+ libraryVersion = "custom_build"
+ }
+ return libraryVersion
+}
+
+final String VALUE_SEPARATOR_REGEX = "\\|"
// get the list of ABIs the project should be exported to
ext.getExportEnabledABIs = { ->
@@ -53,7 +106,7 @@ ext.getExportEnabledABIs = { ->
enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|"
}
Set<String> exportAbiFilter = [];
- for (String abi_name : enabledABIs.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ for (String abi_name : enabledABIs.split(VALUE_SEPARATOR_REGEX)) {
if (!abi_name.trim().isEmpty()){
exportAbiFilter.add(abi_name);
}
@@ -88,7 +141,7 @@ ext.getGodotPluginsMavenRepos = { ->
if (project.hasProperty("plugins_maven_repos")) {
String mavenReposProperty = project.property("plugins_maven_repos")
if (mavenReposProperty != null && !mavenReposProperty.trim().isEmpty()) {
- for (String mavenRepoUrl : mavenReposProperty.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ for (String mavenRepoUrl : mavenReposProperty.split(VALUE_SEPARATOR_REGEX)) {
mavenRepos += mavenRepoUrl.trim()
}
}
@@ -108,7 +161,7 @@ ext.getGodotPluginsRemoteBinaries = { ->
if (project.hasProperty("plugins_remote_binaries")) {
String remoteDepsList = project.property("plugins_remote_binaries")
if (remoteDepsList != null && !remoteDepsList.trim().isEmpty()) {
- for (String dep: remoteDepsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ for (String dep: remoteDepsList.split(VALUE_SEPARATOR_REGEX)) {
remoteDeps += dep.trim()
}
}
@@ -127,7 +180,7 @@ ext.getGodotPluginsLocalBinaries = { ->
if (project.hasProperty("plugins_local_binaries")) {
String pluginsList = project.property("plugins_local_binaries")
if (pluginsList != null && !pluginsList.trim().isEmpty()) {
- for (String plugin : pluginsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) {
+ for (String plugin : pluginsList.split(VALUE_SEPARATOR_REGEX)) {
binDeps += plugin.trim()
}
}
@@ -135,3 +188,83 @@ ext.getGodotPluginsLocalBinaries = { ->
return binDeps
}
+
+ext.getDebugKeystoreFile = { ->
+ String keystoreFile = project.hasProperty("debug_keystore_file") ? project.property("debug_keystore_file") : ""
+ if (keystoreFile == null || keystoreFile.isEmpty()) {
+ keystoreFile = "."
+ }
+ return keystoreFile
+}
+
+ext.hasCustomDebugKeystore = { ->
+ File keystoreFile = new File(getDebugKeystoreFile())
+ return keystoreFile.isFile()
+}
+
+ext.getDebugKeystorePassword = { ->
+ String keystorePassword = project.hasProperty("debug_keystore_password") ? project.property("debug_keystore_password") : ""
+ if (keystorePassword == null || keystorePassword.isEmpty()) {
+ keystorePassword = "android"
+ }
+ return keystorePassword
+}
+
+ext.getDebugKeyAlias = { ->
+ String keyAlias = project.hasProperty("debug_keystore_alias") ? project.property("debug_keystore_alias") : ""
+ if (keyAlias == null || keyAlias.isEmpty()) {
+ keyAlias = "androiddebugkey"
+ }
+ return keyAlias
+}
+
+ext.getReleaseKeystoreFile = { ->
+ String keystoreFile = project.hasProperty("release_keystore_file") ? project.property("release_keystore_file") : ""
+ if (keystoreFile == null || keystoreFile.isEmpty()) {
+ keystoreFile = "."
+ }
+ return keystoreFile
+}
+
+ext.getReleaseKeystorePassword = { ->
+ String keystorePassword = project.hasProperty("release_keystore_password") ? project.property("release_keystore_password") : ""
+ return keystorePassword
+}
+
+ext.getReleaseKeyAlias = { ->
+ String keyAlias = project.hasProperty("release_keystore_alias") ? project.property("release_keystore_alias") : ""
+ return keyAlias
+}
+
+ext.isAndroidStudio = { ->
+ def sysProps = System.getProperties()
+ return sysProps != null && sysProps['idea.platform.prefix'] != null
+}
+
+ext.shouldZipAlign = { ->
+ String zipAlignFlag = project.hasProperty("perform_zipalign") ? project.property("perform_zipalign") : ""
+ if (zipAlignFlag == null || zipAlignFlag.isEmpty()) {
+ if (isAndroidStudio()) {
+ zipAlignFlag = "true"
+ } else {
+ zipAlignFlag = "false"
+ }
+ }
+ return Boolean.parseBoolean(zipAlignFlag)
+}
+
+ext.shouldSign = { ->
+ String signFlag = project.hasProperty("perform_signing") ? project.property("perform_signing") : ""
+ if (signFlag == null || signFlag.isEmpty()) {
+ if (isAndroidStudio()) {
+ signFlag = "true"
+ } else {
+ signFlag = "false"
+ }
+ }
+ return Boolean.parseBoolean(signFlag)
+}
+
+ext.shouldNotStrip = { ->
+ return isAndroidStudio() || project.hasProperty("doNotStrip")
+}
diff --git a/platform/android/java/app/gradle.properties b/platform/android/java/app/gradle.properties
new file mode 100644
index 0000000000..0ad8e611ca
--- /dev/null
+++ b/platform/android/java/app/gradle.properties
@@ -0,0 +1,25 @@
+# Godot custom build Gradle settings.
+# These properties apply when running custom build from the Godot editor.
+# NOTE: This should be kept in sync with 'godot/platform/android/java/gradle.properties' except
+# where otherwise specified.
+
+# For more details on how to configure your build environment visit
+# https://www.gradle.org/docs/current/userguide/build_environment.html
+
+android.enableJetifier=true
+android.useAndroidX=true
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx4536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# https://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+org.gradle.warning.mode=all
+
+# Enable resource optimizations for release build.
+# NOTE: This is turned off for template release build in order to support the build legacy process.
+android.enableResourceOptimizations=true
diff --git a/platform/android/java/app/res/drawable/splash.png b/platform/android/java/app/res/drawable-nodpi/splash.png
index 7bddd4325a..7bddd4325a 100644
--- a/platform/android/java/app/res/drawable/splash.png
+++ b/platform/android/java/app/res/drawable-nodpi/splash.png
Binary files differ
diff --git a/platform/android/java/app/res/drawable/splash_bg_color.png b/platform/android/java/app/res/drawable-nodpi/splash_bg_color.png
index 004b6fd508..004b6fd508 100644
--- a/platform/android/java/app/res/drawable/splash_bg_color.png
+++ b/platform/android/java/app/res/drawable-nodpi/splash_bg_color.png
Binary files differ
diff --git a/platform/android/java/app/res/drawable/splash_drawable.xml b/platform/android/java/app/res/drawable/splash_drawable.xml
index 2794a40817..30627b998c 100644
--- a/platform/android/java/app/res/drawable/splash_drawable.xml
+++ b/platform/android/java/app/res/drawable/splash_drawable.xml
@@ -6,7 +6,7 @@
<item>
<bitmap
android:gravity="center"
+ android:filter="false"
android:src="@drawable/splash" />
</item>
-
</layer-list>
diff --git a/platform/android/java/app/res/values/themes.xml b/platform/android/java/app/res/values/themes.xml
index 26912538d3..99f723f5ba 100644
--- a/platform/android/java/app/res/values/themes.xml
+++ b/platform/android/java/app/res/values/themes.xml
@@ -5,5 +5,6 @@
<style name="GodotAppSplashTheme" parent="@style/GodotAppMainTheme">
<item name="android:windowBackground">@drawable/splash_drawable</item>
+ <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
</resources>
diff --git a/platform/android/java/app/settings.gradle b/platform/android/java/app/settings.gradle
index 33b863c7bf..e38d7b2ba6 100644
--- a/platform/android/java/app/settings.gradle
+++ b/platform/android/java/app/settings.gradle
@@ -1,2 +1,2 @@
-// Empty settings.gradle file to denote this directory as being the root project
-// of the Godot custom build.
+// This is the root directory of the Godot custom build.
+include ':assetPacks:installTime'
diff --git a/platform/android/java/app/src/com/godot/game/GodotApp.java b/platform/android/java/app/src/com/godot/game/GodotApp.java
index 51df70969e..955446b0c2 100644
--- a/platform/android/java/app/src/com/godot/game/GodotApp.java
+++ b/platform/android/java/app/src/com/godot/game/GodotApp.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index 821a4dc584..efdcc6c77b 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -5,7 +5,7 @@ buildscript {
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
classpath libraries.androidGradlePlugin
@@ -16,14 +16,11 @@ buildscript {
allprojects {
repositories {
google()
- jcenter()
mavenCentral()
}
}
ext {
- sconsExt = org.gradle.internal.os.OperatingSystem.current().isWindows() ? ".bat" : ""
-
supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"]
supportedTargets = ["release", "debug"]
@@ -114,26 +111,27 @@ task copyReleaseAARToBin(type: Copy) {
* The zip file also includes some gradle tools to allow building of the custom build.
*/
task zipCustomBuild(type: Zip) {
- dependsOn ':generateGodotTemplates'
+ onlyIf { generateGodotTemplates.state.executed || generateDevTemplate.state.executed }
doFirst {
logger.lifecycle("Generating Godot custom build template")
}
- from(fileTree(dir: 'app', excludes: ['**/build/**', '**/.gradle/**', '**/*.iml']), fileTree(dir: '.', includes: ['gradle.properties', 'gradlew', 'gradlew.bat', 'gradle/**']))
+ from(fileTree(dir: 'app', excludes: ['**/build/**', '**/.gradle/**', '**/*.iml']), fileTree(dir: '.', includes: ['gradlew', 'gradlew.bat', 'gradle/**']))
include '**/*'
- archiveName 'android_source.zip'
- destinationDir(file(binDir))
+ archiveFileName = 'android_source.zip'
+ destinationDirectory = file(binDir)
}
-/**
- * Master task used to coordinate the tasks defined above to generate the set of Godot templates.
- */
-task generateGodotTemplates(type: GradleBuild) {
+def templateExcludedBuildTask() {
// We exclude these gradle tasks so we can run the scons command manually.
+ def excludedTasks = []
for (String buildType : supportedTargets) {
- startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType)
+ excludedTasks += ":lib:" + getSconsTaskName(buildType)
}
+ return excludedTasks
+}
- tasks = []
+def templateBuildTasks() {
+ def tasks = []
// Only build the apks and aar files for which we have native shared libraries.
for (String target : supportedTargets) {
@@ -154,6 +152,29 @@ task generateGodotTemplates(type: GradleBuild) {
}
}
+ return tasks
+}
+
+/**
+ * Master task used to coordinate the tasks defined above to generate the set of Godot templates.
+ */
+task generateGodotTemplates {
+ gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
+ dependsOn = templateBuildTasks()
+
+ finalizedBy 'zipCustomBuild'
+}
+
+/**
+ * Generates the same output as generateGodotTemplates but with dev symbols
+ */
+task generateDevTemplate {
+ // add parameter to set symbols to true
+ gradle.startParameter.projectProperties += [doNotStrip: true]
+
+ gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
+ dependsOn = templateBuildTasks()
+
finalizedBy 'zipCustomBuild'
}
@@ -167,12 +188,6 @@ task cleanGodotTemplates(type: Delete) {
// Delete the library generated AAR files
delete("lib/build/outputs/aar")
- // Delete the godotpayment libs directory contents
- delete("plugins/godotpayment/libs")
-
- // Delete the generated godotpayment aar
- delete("plugins/godotpayment/build/outputs/aar")
-
// Delete the app libs directory contents
delete("app/libs")
diff --git a/platform/android/java/gradle.properties b/platform/android/java/gradle.properties
index e14cd5ba5c..5cd94e85d9 100644
--- a/platform/android/java/gradle.properties
+++ b/platform/android/java/gradle.properties
@@ -1,20 +1,28 @@
# Project-wide Gradle settings.
+# NOTE: This should be kept in sync with 'godot/platform/android/java/app/gradle.properties' except
+# where otherwise specified.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
+# https://www.gradle.org/docs/current/userguide/build_environment.html
android.enableJetifier=true
android.useAndroidX=true
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
+org.gradle.jvmargs=-Xmx4536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# https://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+
+org.gradle.warning.mode=all
+
+# Disable resource optimizations for template release build.
+# NOTE: This is turned on for custom build in order to improve the release build.
+android.enableResourceOptimizations=false
diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.jar b/platform/android/java/gradle/wrapper/gradle-wrapper.jar
index f6b961fd5a..e708b1c023 100644
--- a/platform/android/java/gradle/wrapper/gradle-wrapper.jar
+++ b/platform/android/java/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
index f56b0f6a5e..ffed3a254e 100644
--- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties
+++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Mon Sep 02 02:44:30 PDT 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
diff --git a/platform/android/java/gradlew b/platform/android/java/gradlew
index cccdd3d517..4f906e0c81 100755
--- a/platform/android/java/gradlew
+++ b/platform/android/java/gradlew
@@ -1,5 +1,21 @@
#!/usr/bin/env sh
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
##############################################################################
##
## Gradle start up script for UN*X
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@@ -66,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -109,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -138,19 +156,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
- i=$((i+1))
+ i=`expr $i + 1`
done
case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -159,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
-APP_ARGS=$(save "$@")
+APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
-fi
-
exec "$JAVACMD" "$@"
diff --git a/platform/android/java/gradlew.bat b/platform/android/java/gradlew.bat
index f9553162f1..107acd32c4 100644
--- a/platform/android/java/gradlew.bat
+++ b/platform/android/java/gradlew.bat
@@ -1,3 +1,19 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,28 +64,14 @@ echo location of your Java installation.
goto fail
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/platform/android/java/lib/AndroidManifest.xml b/platform/android/java/lib/AndroidManifest.xml
index fa39bc0f1d..2de62271c4 100644
--- a/platform/android/java/lib/AndroidManifest.xml
+++ b/platform/android/java/lib/AndroidManifest.xml
@@ -4,8 +4,16 @@
android:versionCode="1"
android:versionName="1.0">
+ <!-- Should match the mindSdk and targetSdk values in platform/android/java/app/config.gradle -->
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="30" />
+
<application>
+ <!-- Records the version of the Godot library -->
+ <meta-data
+ android:name="org.godotengine.library.version"
+ android:value="${godotLibraryVersion}" />
+
<service android:name=".GodotDownloaderService" />
</application>
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index 19eee5a315..fbed4ed078 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -2,9 +2,8 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
dependencies {
- implementation libraries.supportCoreUtils
implementation libraries.kotlinStdLib
- implementation libraries.v4Support
+ implementation libraries.androidxFragment
}
def pathToRootDir = "../../../../"
@@ -13,9 +12,18 @@ android {
compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools
+ ndkVersion versions.ndkVersion
+
defaultConfig {
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
+
+ manifestPlaceholders = [godotLibraryVersion: getGodotLibraryVersion()]
+ }
+
+ compileOptions {
+ sourceCompatibility versions.javaVersion
+ targetCompatibility versions.javaVersion
}
lintOptions {
@@ -27,8 +35,10 @@ android {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
- // Should be uncommented for development purpose within Android Studio
- // doNotStrip '**/*.so'
+ // 'doNotStrip' is enabled for development within Android Studio
+ if (shouldNotStrip()) {
+ doNotStrip '**/*.so'
+ }
}
sourceSets {
@@ -50,15 +60,6 @@ android {
def buildType = variant.buildType.name.capitalize()
- def taskPrefix = ""
- if (project.path != ":") {
- taskPrefix = project.path + ":"
- }
-
- // Disable the externalNativeBuild* task as it would cause build failures since the cmake build
- // files is only setup for editing support.
- gradle.startParameter.excludedTaskNames += taskPrefix + "externalNativeBuild" + buildType
-
def releaseTarget = buildType.toLowerCase()
if (releaseTarget == null || releaseTarget == "") {
throw new GradleException("Invalid build type: " + buildType)
@@ -68,20 +69,46 @@ android {
throw new GradleException("Invalid default abi: " + defaultAbi)
}
+ // Find scons' executable path
+ File sconsExecutableFile = null
+ def sconsName = "scons"
+ def sconsExts = (org.gradle.internal.os.OperatingSystem.current().isWindows()
+ ? [".bat", ".cmd", ".ps1", ".exe"]
+ : [""])
+ logger.lifecycle("Looking for $sconsName executable path")
+ for (ext in sconsExts) {
+ String sconsNameExt = sconsName + ext
+ logger.lifecycle("Checking $sconsNameExt")
+
+ sconsExecutableFile = org.gradle.internal.os.OperatingSystem.current().findInPath(sconsNameExt)
+ if (sconsExecutableFile != null) {
+ // We're done!
+ break
+ }
+
+ // Check all the options in path
+ List<File> allOptions = org.gradle.internal.os.OperatingSystem.current().findAllInPath(sconsNameExt)
+ if (!allOptions.isEmpty()) {
+ // Pick the first option and we're done!
+ sconsExecutableFile = allOptions.get(0)
+ break
+ }
+ }
+
+ if (sconsExecutableFile == null) {
+ throw new GradleException("Unable to find executable path for the '$sconsName' command.")
+ } else {
+ logger.lifecycle("Found executable path for $sconsName: ${sconsExecutableFile.absolutePath}")
+ }
+
// Creating gradle task to generate the native libraries for the default abi.
def taskName = getSconsTaskName(buildType)
tasks.create(name: taskName, type: Exec) {
- executable "scons" + sconsExt
+ executable sconsExecutableFile.absolutePath
args "--directory=${pathToRootDir}", "platform=android", "target=${releaseTarget}", "android_arch=${defaultAbi}", "-j" + Runtime.runtime.availableProcessors()
}
// Schedule the tasks so the generated libs are present before the aar file is packaged.
tasks["merge${buildType}JniLibFolders"].dependsOn taskName
}
-
- externalNativeBuild {
- cmake {
- path "CMakeLists.txt"
- }
- }
}
diff --git a/platform/android/java/lib/res/values/strings.xml b/platform/android/java/lib/res/values/strings.xml
index 590b066d8a..010006b81e 100644
--- a/platform/android/java/lib/res/values/strings.xml
+++ b/platform/android/java/lib/res/values/strings.xml
@@ -6,7 +6,7 @@
<string name="text_button_resume_cellular">Resume download</string>
<string name="text_button_wifi_settings">Wi-Fi settings</string>
<string name="text_verifying_download">Verifying Download</string>
- <string name="text_validation_complete">XAPK File Validation Complete. Select OK to exit.</string>
+ <string name="text_validation_complete">XAPK File Validation Complete. Select OK to exit.</string>
<string name="text_validation_failed">XAPK File Validation Failed.</string>
<string name="text_button_pause">Pause Download</string>
<string name="text_button_resume">Resume Download</string>
diff --git a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java
index 2a72c9818d..9aa65fd786 100644
--- a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java
+++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java
@@ -54,7 +54,7 @@ public class Helpers {
/*
* Parse the Content-Disposition HTTP Header. The format of the header is defined here:
- * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
+ * https://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
* content that is going to be downloaded to the file system. We only support the attachment
* type.
*/
diff --git a/platform/android/java/lib/src/com/google/android/vending/licensing/Obfuscator.java b/platform/android/java/lib/src/com/google/android/vending/licensing/Obfuscator.java
index 008c150a8e..05b452d0c1 100644
--- a/platform/android/java/lib/src/com/google/android/vending/licensing/Obfuscator.java
+++ b/platform/android/java/lib/src/com/google/android/vending/licensing/Obfuscator.java
@@ -20,7 +20,7 @@ package com.google.android.vending.licensing;
* Interface used as part of a {@link Policy} to allow application authors to obfuscate
* licensing data that will be stored into a SharedPreferences file.
* <p>
- * Any transformation scheme must be reversable. Implementing classes may optionally implement an
+ * Any transformation scheme must be reversible. Implementing classes may optionally implement an
* integrity check to further prevent modification to preference data. Implementing classes
* should use device-specific information as a key in the obfuscation algorithm to prevent
* obfuscated preferences from being shared among devices.
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java b/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java
index 8b7a9c6c74..b12844702a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -43,10 +43,10 @@ public class Dictionary extends HashMap<String, Object> {
for (String key : keys) {
ret[i] = key;
i++;
- };
+ }
return ret;
- };
+ }
public Object[] get_values() {
Object[] ret = new Object[size()];
@@ -55,21 +55,21 @@ public class Dictionary extends HashMap<String, Object> {
for (String key : keys) {
ret[i] = get(key);
i++;
- };
+ }
return ret;
- };
+ }
public void set_keys(String[] keys) {
keys_cache = keys;
- };
+ }
public void set_values(Object[] vals) {
int i = 0;
for (String key : keys_cache) {
put(key, vals[i]);
i++;
- };
+ }
keys_cache = null;
- };
-};
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
index 5aa48d87da..3600706c7c 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,12 +30,15 @@
package org.godotengine.godot;
+import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
-import android.view.KeyEvent;
+import android.util.Log;
+import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
/**
@@ -44,7 +47,9 @@ import androidx.fragment.app.FragmentActivity;
* It's also a reference implementation for how to setup and use the {@link Godot} fragment
* within an Android app.
*/
-public abstract class FullScreenGodotApp extends FragmentActivity {
+public abstract class FullScreenGodotApp extends FragmentActivity implements GodotHost {
+ private static final String TAG = FullScreenGodotApp.class.getSimpleName();
+
@Nullable
private Godot godotFragment;
@@ -52,36 +57,88 @@ public abstract class FullScreenGodotApp extends FragmentActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.godot_app_layout);
- godotFragment = initGodotInstance();
- if (godotFragment == null) {
- throw new IllegalStateException("Godot instance must be non-null.");
+
+ Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.godot_fragment_container);
+ if (currentFragment instanceof Godot) {
+ Log.v(TAG, "Reusing existing Godot fragment instance.");
+ godotFragment = (Godot)currentFragment;
+ } else {
+ Log.v(TAG, "Creating new Godot fragment instance.");
+ godotFragment = initGodotInstance();
+ if (godotFragment == null) {
+ throw new IllegalStateException("Godot instance must be non-null.");
+ }
+
+ getSupportFragmentManager().beginTransaction().replace(R.id.godot_fragment_container, godotFragment).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ onGodotForceQuit(godotFragment);
+ }
+
+ @Override
+ public final void onGodotForceQuit(Godot instance) {
+ if (instance == godotFragment) {
+ System.exit(0);
}
+ }
- getSupportFragmentManager().beginTransaction().replace(R.id.godot_fragment_container, godotFragment).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss();
+ @Override
+ public final void onGodotRestartRequested(Godot instance) {
+ if (instance == godotFragment) {
+ // HACK:
+ //
+ // Currently it's very hard to properly deinitialize Godot on Android to restart the game
+ // from scratch. Therefore, we need to kill the whole app process and relaunch it.
+ //
+ // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including
+ // releasing and reloading native libs or resetting their state somehow and clearing statics).
+ //
+ // Using instrumentation is a way of making the whole app process restart, because Android
+ // will kill any process of the same package which was already running.
+ //
+ Bundle args = new Bundle();
+ args.putParcelable("intent", getIntent());
+ startInstrumentation(new ComponentName(this, GodotInstrumentation.class), null, args);
+ }
}
@Override
public void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
if (godotFragment != null) {
godotFragment.onNewIntent(intent);
}
}
+ @CallSuper
@Override
- public void onBackPressed() {
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
if (godotFragment != null) {
- godotFragment.onBackPressed();
- } else {
- super.onBackPressed();
+ godotFragment.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ @CallSuper
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (godotFragment != null) {
+ godotFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
@Override
- public boolean onKeyMultiple(final int inKeyCode, int repeatCount, KeyEvent event) {
- if (godotFragment != null && godotFragment.onKeyMultiple(inKeyCode, repeatCount, event)) {
- return true;
+ public void onBackPressed() {
+ if (godotFragment != null) {
+ godotFragment.onBackPressed();
+ } else {
+ super.onBackPressed();
}
- return super.onKeyMultiple(inKeyCode, repeatCount, event);
}
/**
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
index 524f32bf5e..b6476fa61a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -49,7 +49,6 @@ import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
@@ -68,16 +67,12 @@ import android.os.Environment;
import android.os.Messenger;
import android.os.VibrationEffect;
import android.os.Vibrator;
-import android.provider.Settings.Secure;
import android.view.Display;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
-import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
-import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
@@ -102,6 +97,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.MessageDigest;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -123,13 +119,13 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
private Button mWiFiSettingsButton;
private XRMode xrMode = XRMode.REGULAR;
- private boolean use_32_bits = false;
private boolean use_immersive = false;
private boolean use_debug_opengl = false;
private boolean mStatePaused;
private boolean activityResumed;
private int mState;
+ private GodotHost godotHost;
private GodotPluginRegistry pluginRegistry;
static private Intent mCurrentIntent;
@@ -172,12 +168,30 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
public static GodotNetUtils netUtils;
public interface ResultCallback {
- public void callback(int requestCode, int resultCode, Intent data);
+ void callback(int requestCode, int resultCode, Intent data);
}
public ResultCallback result_callback;
@Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (getParentFragment() instanceof GodotHost) {
+ godotHost = (GodotHost)getParentFragment();
+ } else if (getActivity() instanceof GodotHost) {
+ godotHost = (GodotHost)getActivity();
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ godotHost = null;
+ }
+
+ @CallSuper
+ @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
if (result_callback != null) {
result_callback.callback(requestCode, resultCode, data);
result_callback = null;
@@ -188,8 +202,10 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
}
+ @CallSuper
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
plugin.onMainRequestPermissionsResult(requestCode, permissions, grantResults);
}
@@ -197,7 +213,21 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
for (int i = 0; i < permissions.length; i++) {
GodotLib.requestPermissionResult(permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED);
}
- };
+ }
+
+ /**
+ * Invoked on the render thread when the Godot setup is complete.
+ */
+ @CallSuper
+ protected void onGodotSetupCompleted() {
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onGodotSetupCompleted();
+ }
+
+ if (godotHost != null) {
+ godotHost.onGodotSetupCompleted();
+ }
+ }
/**
* Invoked on the render thread when the Godot main loop has started.
@@ -207,6 +237,10 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
plugin.onGodotMainLoopStarted();
}
+
+ if (godotHost != null) {
+ godotHost.onGodotMainLoopStarted();
+ }
}
/**
@@ -227,12 +261,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
GodotLib.setup(command_line);
- final String videoDriver = GodotLib.getGlobal("rendering/quality/driver/driver_name");
- if (videoDriver.equals("Vulkan")) {
+ final String videoDriver = GodotLib.getGlobal("rendering/driver/driver_name");
+ if (videoDriver.equals("vulkan")) {
mRenderView = new GodotVulkanRenderView(activity, this);
} else {
- mRenderView = new GodotGLRenderView(activity, this, xrMode, use_32_bits,
- use_debug_opengl);
+ mRenderView = new GodotGLRenderView(activity, this, xrMode, use_debug_opengl);
}
View view = mRenderView.getView();
@@ -240,49 +273,41 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
editText.setView(mRenderView);
io.setEdit(editText);
- view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- Point fullSize = new Point();
- activity.getWindowManager().getDefaultDisplay().getSize(fullSize);
- Rect gameSize = new Rect();
- mRenderView.getView().getWindowVisibleDisplayFrame(gameSize);
-
- final int keyboardHeight = fullSize.y - gameSize.bottom;
- GodotLib.setVirtualKeyboardHeight(keyboardHeight);
- }
+ view.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+ Point fullSize = new Point();
+ activity.getWindowManager().getDefaultDisplay().getSize(fullSize);
+ Rect gameSize = new Rect();
+ mRenderView.getView().getWindowVisibleDisplayFrame(gameSize);
+ final int keyboardHeight = fullSize.y - gameSize.bottom;
+ GodotLib.setVirtualKeyboardHeight(keyboardHeight);
});
- mRenderView.queueOnRenderThread(new Runnable() {
- @Override
- public void run() {
- // Must occur after GodotLib.setup has completed.
- for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
- plugin.onRegisterPluginWithGodotNative();
- }
-
- setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on")));
+ mRenderView.queueOnRenderThread(() -> {
+ for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
+ plugin.onRegisterPluginWithGodotNative();
}
+ setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on")));
});
// Include the returned non-null views in the Godot view hierarchy.
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
View pluginView = plugin.onMainCreate(activity);
if (pluginView != null) {
- containerLayout.addView(pluginView);
+ if (plugin.shouldBeOnTop()) {
+ containerLayout.addView(pluginView);
+ } else {
+ containerLayout.addView(pluginView, 0);
+ }
}
}
}
public void setKeepScreenOn(final boolean p_enabled) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (p_enabled) {
- getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- } else {
- getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
+ runOnUiThread(() -> {
+ if (p_enabled) {
+ getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
});
}
@@ -294,13 +319,13 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
@SuppressLint("MissingPermission")
@Keep
private void vibrate(int durationMs) {
- if (requestPermission("VIBRATE")) {
+ if (durationMs > 0 && requestPermission("VIBRATE")) {
Vibrator v = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE);
if (v != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
v.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
- //deprecated in API 26
+ // deprecated in API 26
v.vibrate(durationMs);
}
}
@@ -308,42 +333,21 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
public void restart() {
- // HACK:
- //
- // Currently it's very hard to properly deinitialize Godot on Android to restart the game
- // from scratch. Therefore, we need to kill the whole app process and relaunch it.
- //
- // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including
- // releasing and reloading native libs or resetting their state somehow and clearing statics).
- //
- // Using instrumentation is a way of making the whole app process restart, because Android
- // will kill any process of the same package which was already running.
- //
- final Activity activity = getActivity();
- if (activity != null) {
- Bundle args = new Bundle();
- args.putParcelable("intent", mCurrentIntent);
- activity.startInstrumentation(new ComponentName(activity, GodotInstrumentation.class), null, args);
+ if (godotHost != null) {
+ godotHost.onGodotRestartRequested(this);
}
}
public void alert(final String message, final String title) {
final Activity activity = getActivity();
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- AlertDialog.Builder builder = new AlertDialog.Builder(activity);
- builder.setMessage(message).setTitle(title);
- builder.setPositiveButton(
- "OK",
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- dialog.cancel();
- }
- });
- AlertDialog dialog = builder.create();
- dialog.show();
- }
+ runOnUiThread(() -> {
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ builder.setMessage(message).setTitle(title);
+ builder.setPositiveButton(
+ "OK",
+ (dialog, id) -> dialog.cancel());
+ AlertDialog dialog = builder.create();
+ dialog.show();
});
}
@@ -355,6 +359,21 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
@CallSuper
protected String[] getCommandLine() {
+ String[] original = parseCommandLine();
+ String[] updated;
+ List<String> hostCommandLine = godotHost != null ? godotHost.getCommandLine() : null;
+ if (hostCommandLine == null || hostCommandLine.isEmpty()) {
+ updated = original;
+ } else {
+ updated = Arrays.copyOf(original, original.length + hostCommandLine.size());
+ for (int i = 0; i < hostCommandLine.size(); i++) {
+ updated[original.length + i] = hostCommandLine.get(i);
+ }
+ }
+ return updated;
+ }
+
+ private String[] parseCommandLine() {
InputStream is;
try {
is = getActivity().getAssets().open("_cl_");
@@ -436,7 +455,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
final Activity activity = getActivity();
io = new GodotIO(activity);
- io.unique_id = Secure.getString(activity.getContentResolver(), Secure.ANDROID_ID);
GodotLib.io = io;
netUtils = new GodotNetUtils(activity);
mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
@@ -463,20 +481,22 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) {
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
final Activity activity = getActivity();
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE);
pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this);
- //check for apk expansion API
+ // check for apk expansion API
boolean md5mismatch = false;
command_line = getCommandLine();
String main_pack_md5 = null;
String main_pack_key = null;
- List<String> new_args = new LinkedList<String>();
+ List<String> new_args = new LinkedList<>();
for (int i = 0; i < command_line.length; i++) {
boolean has_extra = i < command_line.length - 1;
@@ -484,8 +504,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
xrMode = XRMode.REGULAR;
} else if (command_line[i].equals(XRMode.OVR.cmdLineArg)) {
xrMode = XRMode.OVR;
- } else if (command_line[i].equals("--use_depth_32")) {
- use_32_bits = true;
} else if (command_line[i].equals("--debug_opengl")) {
use_debug_opengl = true;
} else if (command_line[i].equals("--use_immersive")) {
@@ -526,9 +544,9 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
command_line = new_args.toArray(new String[new_args.size()]);
}
if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) {
- //check that environment is ok!
+ // check that environment is ok!
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- //show popup and die
+ // show popup and die
}
// Build the full path to the app's expansion files
@@ -556,8 +574,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
if (!pack_valid) {
Intent notifierIntent = new Intent(activity, activity.getClass());
- notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0,
notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
@@ -571,24 +588,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
// This is where you do set up to display the download
- // progress (next step)
+ // progress (next step in onCreateView)
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this,
GodotDownloaderService.class);
- View downloadingExpansionView =
- inflater.inflate(R.layout.downloading_expansion, container, false);
- mPB = (ProgressBar)downloadingExpansionView.findViewById(R.id.progressBar);
- mStatusText = (TextView)downloadingExpansionView.findViewById(R.id.statusText);
- mProgressFraction = (TextView)downloadingExpansionView.findViewById(R.id.progressAsFraction);
- mProgressPercent = (TextView)downloadingExpansionView.findViewById(R.id.progressAsPercentage);
- mAverageSpeed = (TextView)downloadingExpansionView.findViewById(R.id.progressAverageSpeed);
- mTimeRemaining = (TextView)downloadingExpansionView.findViewById(R.id.progressTimeRemaining);
- mDashboard = downloadingExpansionView.findViewById(R.id.downloaderDashboard);
- mCellMessage = downloadingExpansionView.findViewById(R.id.approveCellular);
- mPauseButton = (Button)downloadingExpansionView.findViewById(R.id.pauseButton);
- mWiFiSettingsButton = (Button)downloadingExpansionView.findViewById(R.id.wifiSettingsButton);
-
- return downloadingExpansionView;
+ return;
}
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
@@ -599,6 +603,27 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
mCurrentIntent = activity.getIntent();
initializeGodot();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) {
+ if (mDownloaderClientStub != null) {
+ View downloadingExpansionView =
+ inflater.inflate(R.layout.downloading_expansion, container, false);
+ mPB = (ProgressBar)downloadingExpansionView.findViewById(R.id.progressBar);
+ mStatusText = (TextView)downloadingExpansionView.findViewById(R.id.statusText);
+ mProgressFraction = (TextView)downloadingExpansionView.findViewById(R.id.progressAsFraction);
+ mProgressPercent = (TextView)downloadingExpansionView.findViewById(R.id.progressAsPercentage);
+ mAverageSpeed = (TextView)downloadingExpansionView.findViewById(R.id.progressAverageSpeed);
+ mTimeRemaining = (TextView)downloadingExpansionView.findViewById(R.id.progressTimeRemaining);
+ mDashboard = downloadingExpansionView.findViewById(R.id.downloaderDashboard);
+ mCellMessage = downloadingExpansionView.findViewById(R.id.approveCellular);
+ mPauseButton = (Button)downloadingExpansionView.findViewById(R.id.pauseButton);
+ mWiFiSettingsButton = (Button)downloadingExpansionView.findViewById(R.id.wifiSettingsButton);
+
+ return downloadingExpansionView;
+ }
+
return containerLayout;
}
@@ -612,8 +637,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
super.onDestroy();
- // TODO: This is a temp solution. The proper fix will involve tracking down and properly shutting down each
- // native Godot components that is started in Godot#onVideoInit.
forceQuit();
}
@@ -689,19 +712,16 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
public void UiChangeListener() {
final View decorView = getActivity().getWindow().getDecorView();
- decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
- @Override
- public void onSystemUiVisibilityChange(int visibility) {
- if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- decorView.setSystemUiVisibility(
- View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_FULLSCREEN |
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
- }
+ decorView.setOnSystemUiVisibilityChangeListener(visibility -> {
+ if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ decorView.setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
}
});
@@ -714,7 +734,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
int displayRotation = display.getRotation();
float[] adjustedValues = new float[3];
- final int axisSwap[][] = {
+ final int[][] axisSwap = {
{ 1, -1, 0, 1 }, // ROTATION_0
{ -1, -1, 1, 0 }, // ROTATION_90
{ -1, 1, 0, 1 }, // ROTATION_180
@@ -732,21 +752,18 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
final int typeOfSensor = event.sensor.getType();
if (mRenderView != null) {
- mRenderView.queueOnRenderThread(new Runnable() {
- @Override
- public void run() {
- if (typeOfSensor == Sensor.TYPE_ACCELEROMETER) {
- GodotLib.accelerometer(-x, y, -z);
- }
- if (typeOfSensor == Sensor.TYPE_GRAVITY) {
- GodotLib.gravity(-x, y, -z);
- }
- if (typeOfSensor == Sensor.TYPE_MAGNETIC_FIELD) {
- GodotLib.magnetometer(-x, y, -z);
- }
- if (typeOfSensor == Sensor.TYPE_GYROSCOPE) {
- GodotLib.gyroscope(x, -y, z);
- }
+ mRenderView.queueOnRenderThread(() -> {
+ if (typeOfSensor == Sensor.TYPE_ACCELEROMETER) {
+ GodotLib.accelerometer(-x, y, -z);
+ }
+ if (typeOfSensor == Sensor.TYPE_GRAVITY) {
+ GodotLib.gravity(-x, y, -z);
+ }
+ if (typeOfSensor == Sensor.TYPE_MAGNETIC_FIELD) {
+ GodotLib.magnetometer(-x, y, -z);
+ }
+ if (typeOfSensor == Sensor.TYPE_GYROSCOPE) {
+ GodotLib.gyroscope(x, -y, z);
}
});
}
@@ -759,9 +776,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
/*
@Override public boolean dispatchKeyEvent(KeyEvent event) {
-
if (event.getKeyCode()==KeyEvent.KEYCODE_BACK) {
-
System.out.printf("** BACK REQUEST!\n");
GodotLib.quit();
@@ -783,12 +798,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
if (shouldQuit && mRenderView != null) {
- mRenderView.queueOnRenderThread(new Runnable() {
- @Override
- public void run() {
- GodotLib.back();
- }
- });
+ mRenderView.queueOnRenderThread(GodotLib::back);
}
}
@@ -810,7 +820,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
private void forceQuit() {
- System.exit(0);
+ // TODO: This is a temp solution. The proper fix will involve tracking down and properly shutting down each
+ // native Godot components that is started in Godot#onVideoInit.
+ if (godotHost != null) {
+ godotHost.onGodotForceQuit(this);
+ }
}
private boolean obbIsCorrupted(String f, String main_pack_md5) {
@@ -833,7 +847,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
byte[] messageDigest = complete.digest();
// Create Hex String
- StringBuffer hexString = new StringBuffer();
+ StringBuilder hexString = new StringBuilder();
for (int i = 0; i < messageDigest.length; i++) {
String s = Integer.toHexString(0xFF & messageDigest[i]);
@@ -854,90 +868,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
}
- public boolean gotTouchEvent(final MotionEvent event) {
- final int evcount = event.getPointerCount();
- if (evcount == 0)
- return true;
-
- if (mRenderView != null) {
- final int[] arr = new int[event.getPointerCount() * 3];
-
- for (int i = 0; i < event.getPointerCount(); i++) {
- arr[i * 3 + 0] = (int)event.getPointerId(i);
- arr[i * 3 + 1] = (int)event.getX(i);
- arr[i * 3 + 2] = (int)event.getY(i);
- }
- final int pointer_idx = event.getPointerId(event.getActionIndex());
-
- //System.out.printf("gaction: %d\n",event.getAction());
- final int action = event.getAction() & MotionEvent.ACTION_MASK;
- mRenderView.queueOnRenderThread(new Runnable() {
- @Override
- public void run() {
- switch (action) {
- case MotionEvent.ACTION_DOWN: {
- GodotLib.touch(0, 0, evcount, arr);
- //System.out.printf("action down at: %f,%f\n", event.getX(),event.getY());
- } break;
- case MotionEvent.ACTION_MOVE: {
- GodotLib.touch(1, 0, evcount, arr);
- /*
- for(int i=0;i<event.getPointerCount();i++) {
- System.out.printf("%d - moved to: %f,%f\n",i, event.getX(i),event.getY(i));
- }
- */
- } break;
- case MotionEvent.ACTION_POINTER_UP: {
- GodotLib.touch(4, pointer_idx, evcount, arr);
- //System.out.printf("%d - s.up at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx));
- } break;
- case MotionEvent.ACTION_POINTER_DOWN: {
- GodotLib.touch(3, pointer_idx, evcount, arr);
- //System.out.printf("%d - s.down at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx));
- } break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP: {
- GodotLib.touch(2, 0, evcount, arr);
- /*
- for(int i=0;i<event.getPointerCount();i++) {
- System.out.printf("%d - up! %f,%f\n",i, event.getX(i),event.getY(i));
- }
- */
- } break;
- }
- }
- });
- }
- return true;
- }
-
- public boolean onKeyMultiple(final int inKeyCode, int repeatCount, KeyEvent event) {
- String s = event.getCharacters();
- if (s == null || s.length() == 0)
- return false;
-
- final char[] cc = s.toCharArray();
- int cnt = 0;
- for (int i = cc.length; --i >= 0; cnt += cc[i] != 0 ? 1 : 0)
- ;
- if (cnt == 0)
- return false;
- mRenderView.queueOnRenderThread(new Runnable() {
- // This method will be called on the rendering thread:
- public void run() {
- for (int i = 0, n = cc.length; i < n; i++) {
- int keyCode;
- if ((keyCode = cc[i]) != 0) {
- // Simulate key down and up...
- GodotLib.key(0, 0, keyCode, true);
- GodotLib.key(0, 0, keyCode, false);
- }
- }
- }
- });
- return true;
- }
-
public boolean requestPermission(String p_name) {
return PermissionsUtil.requestPermission(p_name, getActivity());
}
@@ -1046,4 +976,9 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
public void initInputDevices() {
mRenderView.initInputDevices();
}
+
+ @Keep
+ private GodotRenderView getRenderView() { // used by native side to get renderView
+ return mRenderView;
+ }
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java
index a3dae15980..9784d51182 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java
index 434da95bc0..09337ef989 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -50,9 +50,9 @@ public class GodotDownloaderService extends DownloaderService {
};
/**
- * This public key comes from your Android Market publisher account, and it
- * used by the LVL to validate responses from Market on your behalf.
- */
+ * This public key comes from your Android Market publisher account, and it
+ * used by the LVL to validate responses from Market on your behalf.
+ */
@Override
public String getPublicKey() {
SharedPreferences prefs = getApplicationContext().getSharedPreferences("app_data_keys", Context.MODE_PRIVATE);
@@ -63,20 +63,20 @@ public class GodotDownloaderService extends DownloaderService {
}
/**
- * This is used by the preference obfuscater to make sure that your
- * obfuscated preferences are different than the ones used by other
- * applications.
- */
+ * This is used by the preference obfuscater to make sure that your
+ * obfuscated preferences are different than the ones used by other
+ * applications.
+ */
@Override
public byte[] getSALT() {
return SALT;
}
/**
- * Fill this in with the class name for your alarm receiver. We do this
- * because receivers must be unique across all of Android (it's a good idea
- * to make sure that your receiver is in your unique package)
- */
+ * Fill this in with the class name for your alarm receiver. We do this
+ * because receivers must be unique across all of Android (it's a good idea
+ * to make sure that your receiver is in your unique package)
+ */
@Override
public String getAlarmReceiverClassName() {
Log.d("GODOT", "getAlarmReceiverClassName()");
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
index d169f46599..088b048502 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -29,7 +29,6 @@
/*************************************************************************/
package org.godotengine.godot;
-
import org.godotengine.godot.input.GodotGestureHandler;
import org.godotengine.godot.input.GodotInputHandler;
import org.godotengine.godot.utils.GLUtils;
@@ -45,11 +44,15 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
+import android.os.Build;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.PointerIcon;
import android.view.SurfaceView;
+import androidx.annotation.Keep;
+
/**
* A simple GLSurfaceView sub-class that demonstrate how to perform
* OpenGL ES 2.0 rendering into a GL Surface. Note the following important
@@ -73,18 +76,20 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
private final GodotInputHandler inputHandler;
private final GestureDetector detector;
private final GodotRenderer godotRenderer;
+ private PointerIcon pointerIcon;
- public GodotGLRenderView(Context context, Godot godot, XRMode xrMode, boolean p_use_32_bits,
- boolean p_use_debug_opengl) {
+ public GodotGLRenderView(Context context, Godot godot, XRMode xrMode, boolean p_use_debug_opengl) {
super(context);
- GLUtils.use_32 = p_use_32_bits;
GLUtils.use_debug_opengl = p_use_debug_opengl;
this.godot = godot;
this.inputHandler = new GodotInputHandler(this);
this.detector = new GestureDetector(context, new GodotGestureHandler(this));
this.godotRenderer = new GodotRenderer();
- init(xrMode, false, 16, 0);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ pointerIcon = PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT);
+ }
+ init(xrMode, false);
}
@Override
@@ -127,7 +132,7 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
this.detector.onTouchEvent(event);
- return godot.gotTouchEvent(event);
+ return inputHandler.onTouchEvent(event);
}
@Override
@@ -145,7 +150,27 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
return inputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event);
}
- private void init(XRMode xrMode, boolean translucent, int depth, int stencil) {
+ @Override
+ public boolean onCapturedPointerEvent(MotionEvent event) {
+ return inputHandler.onGenericMotionEvent(event);
+ }
+
+ /**
+ * called from JNI to change pointer icon
+ */
+ @Keep
+ public void setPointerIcon(int pointerType) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType);
+ }
+ }
+
+ @Override
+ public PointerIcon onResolvePointerIcon(MotionEvent me, int pointerIndex) {
+ return pointerIcon;
+ }
+
+ private void init(XRMode xrMode, boolean translucent) {
setPreserveEGLContextOnPause(true);
setFocusableInTouchMode(true);
switch (xrMode) {
@@ -182,18 +207,9 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
* below.
*/
- if (GLUtils.use_32) {
- setEGLConfigChooser(translucent ?
- new RegularFallbackConfigChooser(8, 8, 8, 8, 24, stencil,
- new RegularConfigChooser(8, 8, 8, 8, 16, stencil)) :
- new RegularFallbackConfigChooser(8, 8, 8, 8, 24, stencil,
- new RegularConfigChooser(5, 6, 5, 0, 16, stencil)));
-
- } else {
- setEGLConfigChooser(translucent ?
- new RegularConfigChooser(8, 8, 8, 8, 16, stencil) :
- new RegularConfigChooser(5, 6, 5, 0, 16, stencil));
- }
+ setEGLConfigChooser(
+ new RegularFallbackConfigChooser(8, 8, 8, 8, 24, 0,
+ new RegularConfigChooser(8, 8, 8, 8, 16, 0)));
break;
}
@@ -205,13 +221,10 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
public void onResume() {
super.onResume();
- queueEvent(new Runnable() {
- @Override
- public void run() {
- // Resume the renderer
- godotRenderer.onActivityResumed();
- GodotLib.focusin();
- }
+ queueEvent(() -> {
+ // Resume the renderer
+ godotRenderer.onActivityResumed();
+ GodotLib.focusin();
});
}
@@ -219,13 +232,10 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
public void onPause() {
super.onPause();
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.focusout();
- // Pause the renderer
- godotRenderer.onActivityPaused();
- }
+ queueEvent(() -> {
+ GodotLib.focusout();
+ // Pause the renderer
+ godotRenderer.onActivityPaused();
});
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
new file mode 100644
index 0000000000..7b22895994
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
@@ -0,0 +1,67 @@
+/*************************************************************************/
+/* GodotHost.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+package org.godotengine.godot;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Denotate a component (e.g: Activity, Fragment) that hosts the {@link Godot} fragment.
+ */
+public interface GodotHost {
+ /**
+ * Provides a set of command line parameters to setup the engine.
+ */
+ default List<String> getCommandLine() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Invoked on the render thread when the Godot setup is complete.
+ */
+ default void onGodotSetupCompleted() {}
+
+ /**
+ * Invoked on the render thread when the Godot main loop has started.
+ */
+ default void onGodotMainLoopStarted() {}
+
+ /**
+ * Invoked on the UI thread as the last step of the Godot instance clean up phase.
+ */
+ default void onGodotForceQuit(Godot instance) {}
+
+ /**
+ * Invoked on the GL thread when the Godot instance wants to be restarted. It's up to the host
+ * to perform the appropriate action(s).
+ */
+ default void onGodotRestartRequested(Godot instance) {}
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index c2f3c88416..d85d88ec6c 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,29 +30,37 @@
package org.godotengine.godot;
-import org.godotengine.godot.input.*;
+import org.godotengine.godot.input.GodotEditText;
import android.app.Activity;
-import android.content.*;
+import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
-import android.media.*;
+import android.graphics.Point;
import android.net.Uri;
-import android.os.*;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.Settings;
+import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.WindowInsets;
import java.io.IOException;
-import java.io.InputStream;
import java.util.Locale;
// Wrapper for native library
public class GodotIO {
- AssetManager am;
- final Activity activity;
+ private static final String TAG = GodotIO.class.getSimpleName();
+
+ private final AssetManager am;
+ private final Activity activity;
+ private final String uniqueId;
GodotEditText edit;
final int SCREEN_LANDSCAPE = 0;
@@ -64,179 +72,18 @@ public class GodotIO {
final int SCREEN_SENSOR = 6;
/////////////////////////
- /// FILES
- /////////////////////////
-
- public int last_file_id = 1;
-
- class AssetData {
- public boolean eof = false;
- public String path;
- public InputStream is;
- public int len;
- public int pos;
- }
-
- SparseArray<AssetData> streams;
-
- public int file_open(String path, boolean write) {
- //System.out.printf("file_open: Attempt to Open %s\n",path);
-
- //Log.v("MyApp", "TRYING TO OPEN FILE: " + path);
- if (write)
- return -1;
-
- AssetData ad = new AssetData();
-
- try {
- ad.is = am.open(path);
-
- } catch (Exception e) {
- //System.out.printf("Exception on file_open: %s\n",path);
- return -1;
- }
-
- try {
- ad.len = ad.is.available();
- } catch (Exception e) {
- System.out.printf("Exception availabling on file_open: %s\n", path);
- return -1;
- }
-
- ad.path = path;
- ad.pos = 0;
- ++last_file_id;
- streams.put(last_file_id, ad);
-
- return last_file_id;
- }
- public int file_get_size(int id) {
- if (streams.get(id) == null) {
- System.out.printf("file_get_size: Invalid file id: %d\n", id);
- return -1;
- }
-
- return streams.get(id).len;
- }
- public void file_seek(int id, int bytes) {
- if (streams.get(id) == null) {
- System.out.printf("file_get_size: Invalid file id: %d\n", id);
- return;
- }
- //seek sucks
- AssetData ad = streams.get(id);
- if (bytes > ad.len)
- bytes = ad.len;
- if (bytes < 0)
- bytes = 0;
-
- try {
- if (bytes > (int)ad.pos) {
- int todo = bytes - (int)ad.pos;
- while (todo > 0) {
- todo -= ad.is.skip(todo);
- }
- ad.pos = bytes;
- } else if (bytes < (int)ad.pos) {
- ad.is = am.open(ad.path);
-
- ad.pos = bytes;
- int todo = bytes;
- while (todo > 0) {
- todo -= ad.is.skip(todo);
- }
- }
-
- ad.eof = false;
- } catch (IOException e) {
- System.out.printf("Exception on file_seek: %s\n", e);
- return;
- }
- }
-
- public int file_tell(int id) {
- if (streams.get(id) == null) {
- System.out.printf("file_read: Can't tell eof for invalid file id: %d\n", id);
- return 0;
- }
-
- AssetData ad = streams.get(id);
- return ad.pos;
- }
- public boolean file_eof(int id) {
- if (streams.get(id) == null) {
- System.out.printf("file_read: Can't check eof for invalid file id: %d\n", id);
- return false;
- }
-
- AssetData ad = streams.get(id);
- return ad.eof;
- }
-
- public byte[] file_read(int id, int bytes) {
- if (streams.get(id) == null) {
- System.out.printf("file_read: Can't read invalid file id: %d\n", id);
- return new byte[0];
- }
-
- AssetData ad = streams.get(id);
-
- if (ad.pos + bytes > ad.len) {
- bytes = ad.len - ad.pos;
- ad.eof = true;
- }
-
- if (bytes == 0) {
- return new byte[0];
- }
-
- byte[] buf1 = new byte[bytes];
- int r = 0;
- try {
- r = ad.is.read(buf1);
- } catch (IOException e) {
- System.out.printf("Exception on file_read: %s\n", e);
- return new byte[bytes];
- }
-
- if (r == 0) {
- return new byte[0];
- }
-
- ad.pos += r;
-
- if (r < bytes) {
- byte[] buf2 = new byte[r];
- for (int i = 0; i < r; i++)
- buf2[i] = buf1[i];
- return buf2;
- } else {
- return buf1;
- }
- }
-
- public void file_close(int id) {
- if (streams.get(id) == null) {
- System.out.printf("file_close: Can't close invalid file id: %d\n", id);
- return;
- }
-
- streams.remove(id);
- }
-
- /////////////////////////
/// DIRECTORIES
/////////////////////////
- class AssetDir {
+ static class AssetDir {
public String[] files;
public int current;
public String path;
}
- public int last_dir_id = 1;
+ private int last_dir_id = 1;
- SparseArray<AssetDir> dirs;
+ private final SparseArray<AssetDir> dirs;
public int dir_open(String path) {
AssetDir ad = new AssetDir();
@@ -255,7 +102,6 @@ public class GodotIO {
return -1;
}
- //System.out.printf("Opened dir: %s\n",path);
++last_dir_id;
dirs.put(last_dir_id, ad);
@@ -318,98 +164,14 @@ public class GodotIO {
GodotIO(Activity p_activity) {
am = p_activity.getAssets();
activity = p_activity;
- //streams = new HashMap<Integer, AssetData>();
- streams = new SparseArray<AssetData>();
- dirs = new SparseArray<AssetDir>();
- }
-
- /////////////////////////
- // AUDIO
- /////////////////////////
-
- private Object buf;
- private Thread mAudioThread;
- private AudioTrack mAudioTrack;
-
- public Object audioInit(int sampleRate, int desiredFrames) {
- int channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
- int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
- int frameSize = 4;
-
- System.out.printf("audioInit: initializing audio:\n");
-
- //Log.v("Godot", "Godot audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + ((float)sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
-
- // Let the user pick a larger buffer if they really want -- but ye
- // gods they probably shouldn't, the minimums are horrifyingly high
- // latency already
- desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
-
- mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
- channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
-
- audioStartThread();
-
- //Log.v("Godot", "Godot audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + ((float)mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
-
- buf = new short[desiredFrames * 2];
- return buf;
- }
-
- public void audioStartThread() {
- mAudioThread = new Thread(new Runnable() {
- public void run() {
- mAudioTrack.play();
- GodotLib.audio();
- }
- });
-
- // I'd take REALTIME if I could get it!
- mAudioThread.setPriority(Thread.MAX_PRIORITY);
- mAudioThread.start();
- }
-
- public void audioWriteShortBuffer(short[] buffer) {
- for (int i = 0; i < buffer.length;) {
- int result = mAudioTrack.write(buffer, i, buffer.length - i);
- if (result > 0) {
- i += result;
- } else if (result == 0) {
- try {
- Thread.sleep(1);
- } catch (InterruptedException e) {
- // Nom nom
- }
- } else {
- Log.w("Godot", "Godot audio: error return from write(short)");
- return;
- }
+ dirs = new SparseArray<>();
+ String androidId = Settings.Secure.getString(activity.getContentResolver(),
+ Settings.Secure.ANDROID_ID);
+ if (androidId == null) {
+ androidId = "";
}
- }
-
- public void audioQuit() {
- if (mAudioThread != null) {
- try {
- mAudioThread.join();
- } catch (Exception e) {
- Log.v("Godot", "Problem stopping audio thread: " + e);
- }
- mAudioThread = null;
- //Log.v("Godot", "Finished waiting for audio thread");
- }
-
- if (mAudioTrack != null) {
- mAudioTrack.stop();
- mAudioTrack = null;
- }
- }
-
- public void audioPause(boolean p_pause) {
- if (p_pause)
- mAudioTrack.pause();
- else
- mAudioTrack.play();
+ uniqueId = androidId;
}
/////////////////////////
@@ -418,7 +180,6 @@ public class GodotIO {
public int openURI(String p_uri) {
try {
- Log.v("MyApp", "TRYING TO OPEN URI: " + p_uri);
String path = p_uri;
String type = "";
if (path.startsWith("/")) {
@@ -444,6 +205,10 @@ public class GodotIO {
}
}
+ public String getCacheDir() {
+ return activity.getCacheDir().getAbsolutePath();
+ }
+
public String getDataDir() {
return activity.getFilesDir().getAbsolutePath();
}
@@ -461,18 +226,40 @@ public class GodotIO {
return (int)(metrics.density * 160f);
}
+ public int[] screenGetUsableRect() {
+ DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
+ Display display = activity.getWindowManager().getDefaultDisplay();
+ Point size = new Point();
+ display.getRealSize(size);
+
+ int[] result = { 0, 0, size.x, size.y };
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets();
+ DisplayCutout cutout = insets.getDisplayCutout();
+ if (cutout != null) {
+ int insetLeft = cutout.getSafeInsetLeft();
+ int insetTop = cutout.getSafeInsetTop();
+ result[0] = insetLeft;
+ result[1] = insetTop;
+ result[2] -= insetLeft + cutout.getSafeInsetRight();
+ result[3] -= insetTop + cutout.getSafeInsetBottom();
+ }
+ }
+ return result;
+ }
+
public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (edit != null)
edit.showKeyboard(p_existing_text, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
//InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
//inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
- };
+ }
public void hideKeyboard() {
if (edit != null)
edit.hideKeyboard();
- };
+ }
public void setScreenOrientation(int p_orientation) {
switch (p_orientation) {
@@ -489,13 +276,13 @@ public class GodotIO {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
} break;
case SCREEN_SENSOR_LANDSCAPE: {
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE);
} break;
case SCREEN_SENSOR_PORTRAIT: {
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT);
} break;
case SCREEN_SENSOR: {
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
} break;
}
}
@@ -517,51 +304,58 @@ public class GodotIO {
public static final int SYSTEM_DIR_PICTURES = 6;
public static final int SYSTEM_DIR_RINGTONES = 7;
- public String getSystemDir(int idx) {
- String what = "";
+ public String getSystemDir(int idx, boolean shared_storage) {
+ String what;
switch (idx) {
- case SYSTEM_DIR_DESKTOP: {
- //what=Environment.DIRECTORY_DOCUMENTS;
- what = Environment.DIRECTORY_DOWNLOADS;
+ case SYSTEM_DIR_DESKTOP:
+ default: {
+ what = null; // This leads to the app specific external root directory.
} break;
+
case SYSTEM_DIR_DCIM: {
what = Environment.DIRECTORY_DCIM;
-
} break;
+
case SYSTEM_DIR_DOCUMENTS: {
- what = Environment.DIRECTORY_DOWNLOADS;
- //what=Environment.DIRECTORY_DOCUMENTS;
+ what = Environment.DIRECTORY_DOCUMENTS;
} break;
+
case SYSTEM_DIR_DOWNLOADS: {
what = Environment.DIRECTORY_DOWNLOADS;
-
} break;
+
case SYSTEM_DIR_MOVIES: {
what = Environment.DIRECTORY_MOVIES;
-
} break;
+
case SYSTEM_DIR_MUSIC: {
what = Environment.DIRECTORY_MUSIC;
} break;
+
case SYSTEM_DIR_PICTURES: {
what = Environment.DIRECTORY_PICTURES;
} break;
+
case SYSTEM_DIR_RINGTONES: {
what = Environment.DIRECTORY_RINGTONES;
-
} break;
}
- if (what.equals(""))
- return "";
- return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath();
+ if (shared_storage) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Log.w(TAG, "Shared storage access is limited on Android 10 and higher.");
+ }
+ if (TextUtils.isEmpty(what)) {
+ return Environment.getExternalStorageDirectory().getAbsolutePath();
+ } else {
+ return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath();
+ }
+ } else {
+ return activity.getExternalFilesDir(what).getAbsolutePath();
+ }
}
- protected static final String PREFS_FILE = "device_id.xml";
- protected static final String PREFS_DEVICE_ID = "device_id";
-
- public static String unique_id = "";
public String getUniqueID() {
- return unique_id;
+ return uniqueId;
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotInstrumentation.java b/platform/android/java/lib/src/org/godotengine/godot/GodotInstrumentation.java
index 965e616ef3..7f5fd8627c 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotInstrumentation.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotInstrumentation.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
index 318e2816ff..a23d030d4c 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -34,7 +34,6 @@ import android.app.Activity;
import android.hardware.SensorEvent;
import android.view.Surface;
-import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
@@ -76,9 +75,8 @@ public class GodotLib {
/**
* Invoked on the render thread when the underlying Android surface is created or recreated.
* @param p_surface
- * @param p_32_bits
*/
- public static native void newcontext(Surface p_surface, boolean p_32_bits);
+ public static native void newcontext(Surface p_surface);
/**
* Forward {@link Activity#onBackPressed()} event from the main thread to the GL thread.
@@ -94,17 +92,19 @@ public class GodotLib {
/**
* Forward touch events from the main thread to the GL thread.
*/
- public static native void touch(int what, int pointer, int howmany, int[] arr);
+ public static native void touch(int inputDevice, int event, int pointer, int pointerCount, float[] positions);
+ public static native void touch(int inputDevice, int event, int pointer, int pointerCount, float[] positions, int buttonsMask);
+ public static native void touch(int inputDevice, int event, int pointer, int pointerCount, float[] positions, int buttonsMask, float verticalFactor, float horizontalFactor);
/**
* Forward hover events from the main thread to the GL thread.
*/
- public static native void hover(int type, int x, int y);
+ public static native void hover(int type, float x, float y);
/**
* Forward double_tap events from the main thread to the GL thread.
*/
- public static native void doubletap(int x, int y);
+ public static native void doubleTap(int buttonMask, int x, int y);
/**
* Forward scroll events from the main thread to the GL thread.
@@ -173,11 +173,6 @@ public class GodotLib {
public static native void focusout();
/**
- * Invoked when the audio thread is started.
- */
- public static native void audio();
-
- /**
* Used to access Godot global properties.
* @param p_key Property key
* @return String value of the property
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
index 68b8a16641..79007764a7 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -35,16 +35,18 @@ import org.godotengine.godot.input.GodotInputHandler;
import android.view.SurfaceView;
public interface GodotRenderView {
- abstract public SurfaceView getView();
+ SurfaceView getView();
- abstract public void initInputDevices();
+ void initInputDevices();
- abstract public void queueOnRenderThread(Runnable event);
+ void queueOnRenderThread(Runnable event);
- abstract public void onActivityPaused();
- abstract public void onActivityResumed();
+ void onActivityPaused();
+ void onActivityResumed();
- abstract public void onBackPressed();
+ void onBackPressed();
- abstract public GodotInputHandler getInputHandler();
+ GodotInputHandler getInputHandler();
+
+ void setPointerIcon(int pointerType);
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java
index 64395f7d1e..12e452fc99 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -34,7 +34,6 @@ import org.godotengine.godot.plugin.GodotPlugin;
import org.godotengine.godot.plugin.GodotPluginRegistry;
import org.godotengine.godot.utils.GLUtils;
-import android.content.Context;
import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
@@ -71,7 +70,7 @@ class GodotRenderer implements GLSurfaceView.Renderer {
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
- GodotLib.newcontext(null, GLUtils.use_32);
+ GodotLib.newcontext(null);
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
plugin.onGLSurfaceCreated(gl, config);
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
index 65708389c3..6fca7f2a57 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -37,16 +37,21 @@ import org.godotengine.godot.vulkan.VkSurfaceView;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.os.Build;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.PointerIcon;
import android.view.SurfaceView;
+import androidx.annotation.Keep;
+
public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
private final Godot godot;
private final GodotInputHandler mInputHandler;
private final GestureDetector mGestureDetector;
private final VkRenderer mRenderer;
+ private PointerIcon pointerIcon;
public GodotVulkanRenderView(Context context, Godot godot) {
super(context);
@@ -55,7 +60,9 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
mInputHandler = new GodotInputHandler(this);
mGestureDetector = new GestureDetector(context, new GodotGestureHandler(this));
mRenderer = new VkRenderer();
-
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ pointerIcon = PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT);
+ }
setFocusableInTouchMode(true);
startRenderer(mRenderer);
}
@@ -100,35 +107,52 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
mGestureDetector.onTouchEvent(event);
- return godot.gotTouchEvent(event);
+ return mInputHandler.onTouchEvent(event);
}
@Override
public boolean onKeyUp(final int keyCode, KeyEvent event) {
- return mInputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
+ return mInputHandler.onKeyUp(keyCode, event);
}
@Override
public boolean onKeyDown(final int keyCode, KeyEvent event) {
- return mInputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
+ return mInputHandler.onKeyDown(keyCode, event);
}
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
- return mInputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event);
+ return mInputHandler.onGenericMotionEvent(event);
+ }
+
+ @Override
+ public boolean onCapturedPointerEvent(MotionEvent event) {
+ return mInputHandler.onGenericMotionEvent(event);
+ }
+
+ /**
+ * called from JNI to change pointer icon
+ */
+ @Keep
+ public void setPointerIcon(int pointerType) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType);
+ }
+ }
+
+ @Override
+ public PointerIcon onResolvePointerIcon(MotionEvent me, int pointerIndex) {
+ return pointerIcon;
}
@Override
public void onResume() {
super.onResume();
- queueOnVkThread(new Runnable() {
- @Override
- public void run() {
- // Resume the renderer
- mRenderer.onVkResume();
- GodotLib.focusin();
- }
+ queueOnVkThread(() -> {
+ // Resume the renderer
+ mRenderer.onVkResume();
+ GodotLib.focusin();
});
}
@@ -136,13 +160,10 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
public void onPause() {
super.onPause();
- queueOnVkThread(new Runnable() {
- @Override
- public void run() {
- GodotLib.focusout();
- // Pause the renderer
- mRenderer.onVkPause();
- }
+ queueOnVkThread(() -> {
+ GodotLib.focusout();
+ // Pause the renderer
+ mRenderer.onVkPause();
});
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
index c95339c583..a98ecad594 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -191,9 +191,9 @@ public class GodotEditText extends EditText {
private boolean needHandlingInGodot(int keyCode, KeyEvent keyEvent) {
boolean isArrowKey = keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
- keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT;
+ keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT;
boolean isModifiedKey = keyEvent.isAltPressed() || keyEvent.isCtrlPressed() || keyEvent.isSymPressed() ||
- keyEvent.isFunctionPressed() || keyEvent.isMetaPressed();
+ keyEvent.isFunctionPressed() || keyEvent.isMetaPressed();
return isArrowKey || keyCode == KeyEvent.KEYCODE_TAB || KeyEvent.isModifierKey(keyCode) ||
isModifiedKey;
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java
index 1c9a683bbd..6b248fd034 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -33,7 +33,6 @@ package org.godotengine.godot.input;
import org.godotengine.godot.GodotLib;
import org.godotengine.godot.GodotRenderView;
-import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
@@ -75,12 +74,8 @@ public class GodotGestureHandler extends GestureDetector.SimpleOnGestureListener
//Log.i("GodotGesture", "onDoubleTap");
final int x = Math.round(event.getX());
final int y = Math.round(event.getY());
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.doubletap(x, y);
- }
- });
+ final int buttonMask = event.getButtonState();
+ GodotLib.doubleTap(buttonMask, x, y);
return true;
}
@@ -89,12 +84,7 @@ public class GodotGestureHandler extends GestureDetector.SimpleOnGestureListener
//Log.i("GodotGesture", "onScroll");
final int x = Math.round(distanceX);
final int y = Math.round(distanceY);
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.scroll(x, y);
- }
- });
+ GodotLib.scroll(x, y);
return true;
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
index 9abd65cc67..fc0b84b392 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -36,36 +36,38 @@ import org.godotengine.godot.GodotLib;
import org.godotengine.godot.GodotRenderView;
import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener;
+import android.os.Build;
import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.view.InputDevice;
import android.view.InputDevice.MotionRange;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
/**
* Handles input related events for the {@link GodotRenderView} view.
*/
public class GodotInputHandler implements InputDeviceListener {
- private final ArrayList<Joystick> mJoysticksDevices = new ArrayList<Joystick>();
-
private final GodotRenderView mRenderView;
private final InputManagerCompat mInputManager;
+ private final String tag = this.getClass().getSimpleName();
+
+ private final SparseIntArray mJoystickIds = new SparseIntArray(4);
+ private final SparseArray<Joystick> mJoysticksDevices = new SparseArray<>(4);
+
public GodotInputHandler(GodotRenderView godotView) {
mRenderView = godotView;
mInputManager = InputManagerCompat.Factory.getInputManager(mRenderView.getView().getContext());
mInputManager.registerInputDeviceListener(this, null);
}
- private void queueEvent(Runnable task) {
- mRenderView.queueOnRenderThread(task);
- }
-
private boolean isKeyEvent_GameDevice(int source) {
// Note that keyboards are often (SOURCE_KEYBOARD | SOURCE_DPAD)
if (source == (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD))
@@ -81,32 +83,22 @@ public class GodotInputHandler implements InputDeviceListener {
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
return false;
- };
+ }
int source = event.getSource();
if (isKeyEvent_GameDevice(source)) {
- final int button = getGodotButton(keyCode);
- final int device_id = findJoystickDevice(event.getDeviceId());
-
// Check if the device exists
- if (device_id > -1) {
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.joybutton(device_id, button, false);
- }
- });
+ final int deviceId = event.getDeviceId();
+ if (mJoystickIds.indexOfKey(deviceId) >= 0) {
+ final int button = getGodotButton(keyCode);
+ final int godotJoyId = mJoystickIds.get(deviceId);
+ GodotLib.joybutton(godotJoyId, button, false);
}
} else {
final int scanCode = event.getScanCode();
final int chr = event.getUnicodeChar(0);
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.key(keyCode, scanCode, chr, false);
- }
- });
- };
+ GodotLib.key(keyCode, scanCode, chr, false);
+ }
return true;
}
@@ -121,84 +113,116 @@ public class GodotInputHandler implements InputDeviceListener {
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
return false;
- };
+ }
int source = event.getSource();
//Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD)));
+ final int deviceId = event.getDeviceId();
+ // Check if source is a game device and that the device is a registered gamepad
if (isKeyEvent_GameDevice(source)) {
if (event.getRepeatCount() > 0) // ignore key echo
return true;
- final int button = getGodotButton(keyCode);
- final int device_id = findJoystickDevice(event.getDeviceId());
-
- // Check if the device exists
- if (device_id > -1) {
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.joybutton(device_id, button, true);
- }
- });
+ if (mJoystickIds.indexOfKey(deviceId) >= 0) {
+ final int button = getGodotButton(keyCode);
+ final int godotJoyId = mJoystickIds.get(deviceId);
+ GodotLib.joybutton(godotJoyId, button, true);
}
} else {
final int scanCode = event.getScanCode();
final int chr = event.getUnicodeChar(0);
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.key(keyCode, scanCode, chr, true);
- }
- });
- };
+ GodotLib.key(keyCode, scanCode, chr, true);
+ }
return true;
}
- public boolean onGenericMotionEvent(MotionEvent event) {
- if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) {
- final int device_id = findJoystickDevice(event.getDeviceId());
+ public boolean onTouchEvent(final MotionEvent event) {
+ // Mouse drag (mouse pressed and move) doesn't fire onGenericMotionEvent so this is needed
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ if (event.getAction() != MotionEvent.ACTION_MOVE) {
+ // we return true because every time a mouse event is fired, the event is already handled
+ // in onGenericMotionEvent, so by touch event we can say that the event is also handled
+ return true;
+ }
+ return handleMouseEvent(event);
+ }
+
+ final int evcount = event.getPointerCount();
+ if (evcount == 0)
+ return true;
+
+ if (mRenderView != null) {
+ final float[] arr = new float[event.getPointerCount() * 3]; // pointerId1, x1, y1, pointerId2, etc...
+
+ for (int i = 0; i < event.getPointerCount(); i++) {
+ arr[i * 3 + 0] = event.getPointerId(i);
+ arr[i * 3 + 1] = event.getX(i);
+ arr[i * 3 + 2] = event.getY(i);
+ }
+ final int action = event.getActionMasked();
+ final int pointer_idx = event.getPointerId(event.getActionIndex());
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_POINTER_UP:
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ GodotLib.touch(event.getSource(), action, pointer_idx, evcount, arr);
+ } break;
+ }
+ }
+ return true;
+ }
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ if (event.isFromSource(InputDevice.SOURCE_JOYSTICK) && event.getAction() == MotionEvent.ACTION_MOVE) {
// Check if the device exists
- if (device_id > -1) {
- Joystick joy = mJoysticksDevices.get(device_id);
-
- for (int i = 0; i < joy.axes.size(); i++) {
- InputDevice.MotionRange range = joy.axes.get(i);
- final float value = (event.getAxisValue(range.getAxis()) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
- final int idx = i;
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.joyaxis(device_id, idx, value);
- }
- });
+ final int deviceId = event.getDeviceId();
+ if (mJoystickIds.indexOfKey(deviceId) >= 0) {
+ final int godotJoyId = mJoystickIds.get(deviceId);
+ Joystick joystick = mJoysticksDevices.get(deviceId);
+
+ for (int i = 0; i < joystick.axes.size(); i++) {
+ final int axis = joystick.axes.get(i);
+ final float value = event.getAxisValue(axis);
+ /**
+ * As all axes are polled for each event, only fire an axis event if the value has actually changed.
+ * Prevents flooding Godot with repeated events.
+ */
+ if (joystick.axesValues.indexOfKey(axis) < 0 || (float)joystick.axesValues.get(axis) != value) {
+ // save value to prevent repeats
+ joystick.axesValues.put(axis, value);
+ final int godotAxisIdx = i;
+ GodotLib.joyaxis(godotJoyId, godotAxisIdx, value);
+ }
}
- for (int i = 0; i < joy.hats.size(); i += 2) {
- final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis()));
- final int hatY = Math.round(event.getAxisValue(joy.hats.get(i + 1).getAxis()));
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.joyhat(device_id, hatX, hatY);
- }
- });
+ if (joystick.hasAxisHat) {
+ final int hatX = Math.round(event.getAxisValue(MotionEvent.AXIS_HAT_X));
+ final int hatY = Math.round(event.getAxisValue(MotionEvent.AXIS_HAT_Y));
+ if (joystick.hatX != hatX || joystick.hatY != hatY) {
+ joystick.hatX = hatX;
+ joystick.hatY = hatY;
+ GodotLib.joyhat(godotJoyId, hatX, hatY);
+ }
}
return true;
}
- } else if ((event.getSource() & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) {
- final int x = Math.round(event.getX());
- final int y = Math.round(event.getY());
+ } else if (event.isFromSource(InputDevice.SOURCE_STYLUS)) {
+ final float x = event.getX();
+ final float y = event.getY();
final int type = event.getAction();
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.hover(type, x, y);
- }
- });
+ GodotLib.hover(type, x, y);
return true;
+
+ } else if (event.isFromSource(InputDevice.SOURCE_MOUSE) || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return handleMouseEvent(event);
+ }
}
return false;
@@ -216,67 +240,87 @@ public class GodotInputHandler implements InputDeviceListener {
}
}
+ private int assignJoystickIdNumber(int deviceId) {
+ int godotJoyId = 0;
+ while (mJoystickIds.indexOfValue(godotJoyId) >= 0) {
+ godotJoyId++;
+ }
+ mJoystickIds.put(deviceId, godotJoyId);
+ return godotJoyId;
+ }
+
@Override
public void onInputDeviceAdded(int deviceId) {
- int id = findJoystickDevice(deviceId);
-
// Check if the device has not been already added
- if (id < 0) {
- InputDevice device = mInputManager.getInputDevice(deviceId);
- //device can be null if deviceId is not found
- if (device != null) {
- int sources = device.getSources();
- if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) ||
- ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) {
- id = mJoysticksDevices.size();
-
- Joystick joy = new Joystick();
- joy.device_id = deviceId;
- joy.name = device.getName();
- joy.axes = new ArrayList<InputDevice.MotionRange>();
- joy.hats = new ArrayList<InputDevice.MotionRange>();
-
- List<InputDevice.MotionRange> ranges = device.getMotionRanges();
- Collections.sort(ranges, new RangeComparator());
-
- for (InputDevice.MotionRange range : ranges) {
- if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
- joy.hats.add(range);
- } else {
- joy.axes.add(range);
- }
- }
- mJoysticksDevices.add(joy);
+ if (mJoystickIds.indexOfKey(deviceId) >= 0) {
+ return;
+ }
+
+ InputDevice device = mInputManager.getInputDevice(deviceId);
+ //device can be null if deviceId is not found
+ if (device == null) {
+ return;
+ }
+
+ int sources = device.getSources();
+
+ // Device may not be a joystick or gamepad
+ if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD &&
+ (sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
+ return;
+ }
+
+ // Assign first available number. Re-use numbers where possible.
+ final int id = assignJoystickIdNumber(deviceId);
- final int device_id = id;
- final String name = joy.name;
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.joyconnectionchanged(device_id, true, name);
- }
- });
+ final Joystick joystick = new Joystick();
+ joystick.device_id = deviceId;
+ joystick.name = device.getName();
+
+ //Helps with creating new joypad mappings.
+ Log.i(tag, "=== New Input Device: " + joystick.name);
+
+ Set<Integer> already = new HashSet<>();
+ for (InputDevice.MotionRange range : device.getMotionRanges()) {
+ boolean isJoystick = range.isFromSource(InputDevice.SOURCE_JOYSTICK);
+ boolean isGamepad = range.isFromSource(InputDevice.SOURCE_GAMEPAD);
+ //Log.i(tag, "axis: "+range.getAxis()+ ", isJoystick: "+isJoystick+", isGamepad: "+isGamepad);
+ if (!isJoystick && !isGamepad) {
+ continue;
+ }
+ final int axis = range.getAxis();
+ if (axis == MotionEvent.AXIS_HAT_X || axis == MotionEvent.AXIS_HAT_Y) {
+ joystick.hasAxisHat = true;
+ } else {
+ if (!already.contains(axis)) {
+ already.add(axis);
+ joystick.axes.add(axis);
+ } else {
+ Log.w(tag, " - DUPLICATE AXIS VALUE IN LIST: " + axis);
}
}
}
+ Collections.sort(joystick.axes);
+ for (int idx = 0; idx < joystick.axes.size(); idx++) {
+ //Helps with creating new joypad mappings.
+ Log.i(tag, " - Mapping Android axis " + joystick.axes.get(idx) + " to Godot axis " + idx);
+ }
+ mJoysticksDevices.put(deviceId, joystick);
+
+ GodotLib.joyconnectionchanged(id, true, joystick.name);
}
@Override
public void onInputDeviceRemoved(int deviceId) {
- final int device_id = findJoystickDevice(deviceId);
-
- // Check if the evice has not been already removed
- if (device_id > -1) {
- mJoysticksDevices.remove(device_id);
-
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.joyconnectionchanged(device_id, false, "");
- }
- });
+ // Check if the device has not been already removed
+ if (mJoystickIds.indexOfKey(deviceId) < 0) {
+ return;
}
+ final int godotJoyId = mJoystickIds.get(deviceId);
+ mJoystickIds.delete(deviceId);
+ mJoysticksDevices.delete(deviceId);
+ GodotLib.joyconnectionchanged(godotJoyId, false, "");
}
@Override
@@ -357,13 +401,42 @@ public class GodotInputHandler implements InputDeviceListener {
return button;
}
- private int findJoystickDevice(int device_id) {
- for (int i = 0; i < mJoysticksDevices.size(); i++) {
- if (mJoysticksDevices.get(i).device_id == device_id) {
- return i;
+ private boolean handleMouseEvent(final MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ case MotionEvent.ACTION_HOVER_MOVE:
+ case MotionEvent.ACTION_HOVER_EXIT: {
+ final float x = event.getX();
+ final float y = event.getY();
+ final int type = event.getAction();
+ GodotLib.hover(type, x, y);
+ return true;
+ }
+ case MotionEvent.ACTION_BUTTON_PRESS:
+ case MotionEvent.ACTION_BUTTON_RELEASE:
+ case MotionEvent.ACTION_MOVE: {
+ final float x = event.getX();
+ final float y = event.getY();
+ final int buttonsMask = event.getButtonState();
+ final int action = event.getAction();
+ GodotLib.touch(event.getSource(), action, 0, 1, new float[] { 0, x, y }, buttonsMask);
+ return true;
+ }
+ case MotionEvent.ACTION_SCROLL: {
+ final float x = event.getX();
+ final float y = event.getY();
+ final int buttonsMask = event.getButtonState();
+ final int action = event.getAction();
+ final float verticalFactor = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+ final float horizontalFactor = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
+ GodotLib.touch(event.getSource(), action, 0, 1, new float[] { 0, x, y }, buttonsMask, verticalFactor, horizontalFactor);
+ }
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_UP: {
+ // we can safely ignore these cases because they are always come beside ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE
+ return true;
}
}
-
- return -1;
+ return false;
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
index 4dd1054738..002a75277d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -94,20 +94,15 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
public void beforeTextChanged(final CharSequence pCharSequence, final int start, final int count, final int after) {
//Log.d(TAG, "beforeTextChanged(" + pCharSequence + ")start: " + start + ",count: " + count + ",after: " + after);
- mRenderView.queueOnRenderThread(new Runnable() {
- @Override
- public void run() {
- for (int i = 0; i < count; ++i) {
- GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, true);
- GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, false);
-
- if (mHasSelection) {
- mHasSelection = false;
- break;
- }
- }
+ for (int i = 0; i < count; ++i) {
+ GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, true);
+ GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, false);
+
+ if (mHasSelection) {
+ mHasSelection = false;
+ break;
}
- });
+ }
}
@Override
@@ -118,20 +113,15 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
for (int i = start; i < start + count; ++i) {
newChars[i - start] = pCharSequence.charAt(i);
}
- mRenderView.queueOnRenderThread(new Runnable() {
- @Override
- public void run() {
- for (int i = 0; i < count; ++i) {
- int key = newChars[i];
- if ((key == '\n') && !mEdit.isMultiline()) {
- // Return keys are handled through action events
- continue;
- }
- GodotLib.key(0, 0, key, true);
- GodotLib.key(0, 0, key, false);
- }
+ for (int i = 0; i < count; ++i) {
+ int key = newChars[i];
+ if ((key == '\n') && !mEdit.isMultiline()) {
+ // Return keys are handled through action events
+ continue;
}
- });
+ GodotLib.key(0, 0, key, true);
+ GodotLib.key(0, 0, key, false);
+ }
}
@Override
@@ -139,23 +129,19 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
if (mEdit == pTextView && isFullScreenEdit()) {
final String characters = pKeyEvent.getCharacters();
- mRenderView.queueOnRenderThread(new Runnable() {
- @Override
- public void run() {
- for (int i = 0; i < characters.length(); i++) {
- final int ch = characters.codePointAt(i);
- GodotLib.key(0, 0, ch, true);
- GodotLib.key(0, 0, ch, false);
- }
- }
- });
+ for (int i = 0; i < characters.length(); i++) {
+ final int ch = characters.codePointAt(i);
+ GodotLib.key(0, 0, ch, true);
+ GodotLib.key(0, 0, ch, false);
+ }
}
if (pActionID == EditorInfo.IME_ACTION_DONE) {
// Enter key has been pressed
- GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, true);
- GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, false);
-
+ mRenderView.queueOnRenderThread(() -> {
+ GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, true);
+ GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, false);
+ });
mRenderView.getView().requestFocus();
return true;
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java
index 62810ad3a4..21fdc658bb 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java
@@ -28,14 +28,14 @@ public interface InputManagerCompat {
* @param id The device id
* @return The input device or null if not found
*/
- public InputDevice getInputDevice(int id);
+ InputDevice getInputDevice(int id);
/**
* Gets the ids of all input devices in the system.
*
* @return The input device ids.
*/
- public int[] getInputDeviceIds();
+ int[] getInputDeviceIds();
/**
* Registers an input device listener to receive notifications about when
@@ -46,7 +46,7 @@ public interface InputManagerCompat {
* null if the listener should be invoked on the calling thread's
* looper.
*/
- public void registerInputDeviceListener(InputManagerCompat.InputDeviceListener listener,
+ void registerInputDeviceListener(InputManagerCompat.InputDeviceListener listener,
Handler handler);
/**
@@ -54,7 +54,7 @@ public interface InputManagerCompat {
*
* @param listener The listener to unregister.
*/
- public void unregisterInputDeviceListener(InputManagerCompat.InputDeviceListener listener);
+ void unregisterInputDeviceListener(InputManagerCompat.InputDeviceListener listener);
/*
* The following three calls are to simulate V16 behavior on pre-Jellybean
@@ -69,7 +69,7 @@ public interface InputManagerCompat {
*
* @param event the motion event from the app
*/
- public void onGenericMotionEvent(MotionEvent event);
+ void onGenericMotionEvent(MotionEvent event);
/**
* Tell the V9 input manager that it should stop polling for disconnected
@@ -77,7 +77,7 @@ public interface InputManagerCompat {
* might want to call it whenever your game is not active (or whenever you
* don't care about being notified of new input devices)
*/
- public void onPause();
+ void onPause();
/**
* Tell the V9 input manager that it should start polling for disconnected
@@ -85,9 +85,9 @@ public interface InputManagerCompat {
* might want to call it less often (only when the gameplay is actually
* active)
*/
- public void onResume();
+ void onResume();
- public interface InputDeviceListener {
+ interface InputDeviceListener {
/**
* Called whenever the input manager detects that a device has been
* added. This will only be called in the V9 version when a motion event
@@ -119,7 +119,7 @@ public interface InputManagerCompat {
/**
* Use this to construct a compatible InputManager.
*/
- public static class Factory {
+ class Factory {
/**
* Constructs and returns a compatible InputManger
*
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java
index 61828dccae..0dbc13c77b 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java
@@ -34,7 +34,7 @@ public class InputManagerV16 implements InputManagerCompat {
public InputManagerV16(Context context) {
mInputManager = (InputManager)context.getSystemService(Context.INPUT_SERVICE);
- mListeners = new HashMap<InputManagerCompat.InputDeviceListener, V16InputDeviceListener>();
+ mListeners = new HashMap<>();
}
@Override
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java b/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java
index 1f3fe1e527..bff90d7ce9 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,9 +30,10 @@
package org.godotengine.godot.input;
-import android.view.InputDevice.MotionRange;
+import android.util.SparseArray;
import java.util.ArrayList;
+import java.util.List;
/**
* POJO class to represent a Joystick input device.
@@ -40,6 +41,12 @@ import java.util.ArrayList;
class Joystick {
int device_id;
String name;
- ArrayList<MotionRange> axes;
- ArrayList<MotionRange> hats;
+ List<Integer> axes = new ArrayList<>();
+ protected boolean hasAxisHat = false;
+ /*
+ * Keep track of values so we can prevent flooding the engine with useless events.
+ */
+ protected final SparseArray<Float> axesValues = new SparseArray<>(4);
+ protected int hatX;
+ protected int hatY;
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
index 93c204935c..4536c21ed3 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -46,7 +46,10 @@ import androidx.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -107,56 +110,90 @@ public abstract class GodotPlugin {
* This method is invoked on the render thread.
*/
public final void onRegisterPluginWithGodotNative() {
- nativeRegisterSingleton(getPluginName());
+ registeredSignals.putAll(
+ registerPluginWithGodotNative(this, getPluginName(), getPluginMethods(), getPluginSignals(),
+ getPluginGDNativeLibrariesPaths()));
+ }
+
+ /**
+ * Register the plugin with Godot native code.
+ *
+ * This method must be invoked on the render thread.
+ */
+ public static void registerPluginWithGodotNative(Object pluginObject,
+ GodotPluginInfoProvider pluginInfoProvider) {
+ registerPluginWithGodotNative(pluginObject, pluginInfoProvider.getPluginName(),
+ Collections.emptyList(), pluginInfoProvider.getPluginSignals(),
+ pluginInfoProvider.getPluginGDNativeLibrariesPaths());
+
+ // Notify that registration is complete.
+ pluginInfoProvider.onPluginRegistered();
+ }
+
+ private static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject,
+ String pluginName, List<String> pluginMethods, Set<SignalInfo> pluginSignals,
+ Set<String> pluginGDNativeLibrariesPaths) {
+ nativeRegisterSingleton(pluginName, pluginObject);
+
+ Set<Method> filteredMethods = new HashSet<>();
+ Class<?> clazz = pluginObject.getClass();
- Class clazz = getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
- boolean found = false;
-
- for (String s : getPluginMethods()) {
- if (s.equals(method.getName())) {
- found = true;
- break;
+ // Check if the method is annotated with {@link UsedByGodot}.
+ if (method.getAnnotation(UsedByGodot.class) != null) {
+ filteredMethods.add(method);
+ } else {
+ // For backward compatibility, process the methods from the given <pluginMethods> argument.
+ for (String methodName : pluginMethods) {
+ if (methodName.equals(method.getName())) {
+ filteredMethods.add(method);
+ break;
+ }
}
}
- if (!found)
- continue;
+ }
- List<String> ptr = new ArrayList<String>();
+ for (Method method : filteredMethods) {
+ List<String> ptr = new ArrayList<>();
- Class[] paramTypes = method.getParameterTypes();
- for (Class c : paramTypes) {
+ Class<?>[] paramTypes = method.getParameterTypes();
+ for (Class<?> c : paramTypes) {
ptr.add(c.getName());
}
String[] pt = new String[ptr.size()];
ptr.toArray(pt);
- nativeRegisterMethod(getPluginName(), method.getName(), method.getReturnType().getName(), pt);
+ nativeRegisterMethod(pluginName, method.getName(), method.getReturnType().getName(), pt);
}
// Register the signals for this plugin.
- for (SignalInfo signalInfo : getPluginSignals()) {
+ Map<String, SignalInfo> registeredSignals = new HashMap<>();
+ for (SignalInfo signalInfo : pluginSignals) {
String signalName = signalInfo.getName();
- nativeRegisterSignal(getPluginName(), signalName, signalInfo.getParamTypesNames());
+ nativeRegisterSignal(pluginName, signalName, signalInfo.getParamTypesNames());
registeredSignals.put(signalName, signalInfo);
}
// Get the list of gdnative libraries to register.
- Set<String> gdnativeLibrariesPaths = getPluginGDNativeLibrariesPaths();
- if (!gdnativeLibrariesPaths.isEmpty()) {
- nativeRegisterGDNativeLibraries(gdnativeLibrariesPaths.toArray(new String[0]));
+ if (!pluginGDNativeLibrariesPaths.isEmpty()) {
+ nativeRegisterGDNativeLibraries(pluginGDNativeLibrariesPaths.toArray(new String[0]));
}
+
+ return registeredSignals;
}
/**
* Invoked once during the Godot Android initialization process after creation of the
- * {@link org.godotengine.godot.GodotView} view.
+ * {@link org.godotengine.godot.GodotRenderView} view.
* <p>
* The plugin can return a non-null {@link View} layout in order to add it to the Godot view
* hierarchy.
*
+ * Use shouldBeOnTop() to set whether the plugin's {@link View} should be added on top or behind
+ * the main Godot view.
+ *
* @see Activity#onCreate(Bundle)
* @return the plugin's view to be included; null if no views should be included.
*/
@@ -198,6 +235,11 @@ public abstract class GodotPlugin {
public boolean onMainBackPressed() { return false; }
/**
+ * Invoked on the render thread when the Godot setup is complete.
+ */
+ public void onGodotSetupCompleted() {}
+
+ /**
* Invoked on the render thread when the Godot main loop has started.
*/
public void onGodotMainLoopStarted() {}
@@ -244,8 +286,11 @@ public abstract class GodotPlugin {
/**
* Returns the list of methods to be exposed to Godot.
+ *
+ * @deprecated Used the {@link UsedByGodot} annotation instead.
*/
@NonNull
+ @Deprecated
public List<String> getPluginMethods() {
return Collections.emptyList();
}
@@ -269,6 +314,17 @@ public abstract class GodotPlugin {
}
/**
+ * Returns whether the plugin's {@link View} returned in onMainCreate() should be placed on
+ * top of the main Godot view.
+ *
+ * Returning false causes the plugin's {@link View} to be placed behind, which can be useful
+ * when used with transparency in order to let the Godot view handle inputs.
+ */
+ public boolean shouldBeOnTop() {
+ return true;
+ }
+
+ /**
* Runs the specified action on the UI thread. If the current thread is the UI
* thread, then the action is executed immediately. If the current thread is
* not the UI thread, the action is posted to the event queue of the UI thread.
@@ -290,8 +346,8 @@ public abstract class GodotPlugin {
/**
* Emit a registered Godot signal.
- * @param signalName
- * @param signalArgs
+ * @param signalName Name of the signal to emit. It will be validated against the set of registered signals.
+ * @param signalArgs Arguments used to populate the emitted signal. The arguments will be validated against the {@link SignalInfo} matching the registered signalName parameter.
*/
protected void emitSignal(final String signalName, final Object... signalArgs) {
try {
@@ -301,6 +357,27 @@ public abstract class GodotPlugin {
throw new IllegalArgumentException(
"Signal " + signalName + " is not registered for this plugin.");
}
+ emitSignal(getGodot(), getPluginName(), signalInfo, signalArgs);
+ } catch (IllegalArgumentException exception) {
+ Log.w(TAG, exception.getMessage());
+ if (BuildConfig.DEBUG) {
+ throw exception;
+ }
+ }
+ }
+
+ /**
+ * Emit a Godot signal.
+ * @param godot
+ * @param pluginName Name of the Godot plugin the signal will be emitted from. The plugin must already be registered with the Godot engine.
+ * @param signalInfo Information about the signal to emit.
+ * @param signalArgs Arguments used to populate the emitted signal. The arguments will be validated against the given {@link SignalInfo} parameter.
+ */
+ public static void emitSignal(Godot godot, String pluginName, SignalInfo signalInfo, final Object... signalArgs) {
+ try {
+ if (signalInfo == null) {
+ throw new IllegalArgumentException("Signal must be non null.");
+ }
// Validate the arguments count.
Class<?>[] signalParamTypes = signalInfo.getParamTypes();
@@ -317,12 +394,8 @@ public abstract class GodotPlugin {
}
}
- runOnRenderThread(new Runnable() {
- @Override
- public void run() {
- nativeEmitSignal(getPluginName(), signalName, signalArgs);
- }
- });
+ godot.runOnRenderThread(() -> nativeEmitSignal(pluginName, signalInfo.getName(), signalArgs));
+
} catch (IllegalArgumentException exception) {
Log.w(TAG, exception.getMessage());
if (BuildConfig.DEBUG) {
@@ -335,7 +408,7 @@ public abstract class GodotPlugin {
* Used to setup a {@link GodotPlugin} instance.
* @param p_name Name of the instance.
*/
- private native void nativeRegisterSingleton(String p_name);
+ private static native void nativeRegisterSingleton(String p_name, Object object);
/**
* Used to complete registration of the {@link GodotPlugin} instance's methods.
@@ -344,13 +417,13 @@ public abstract class GodotPlugin {
* @param p_ret Return type of the registered method
* @param p_params Method parameters types
*/
- private native void nativeRegisterMethod(String p_sname, String p_name, String p_ret, String[] p_params);
+ private static native void nativeRegisterMethod(String p_sname, String p_name, String p_ret, String[] p_params);
/**
* Used to register gdnative libraries bundled by the plugin.
* @param gdnlibPaths Paths to the libraries relative to the 'assets' directory.
*/
- private native void nativeRegisterGDNativeLibraries(String[] gdnlibPaths);
+ private static native void nativeRegisterGDNativeLibraries(String[] gdnlibPaths);
/**
* Used to complete registration of the {@link GodotPlugin} instance's methods.
@@ -358,7 +431,7 @@ public abstract class GodotPlugin {
* @param signalName Name of the signal to register
* @param signalParamTypes Signal parameters types
*/
- private native void nativeRegisterSignal(String pluginName, String signalName, String[] signalParamTypes);
+ private static native void nativeRegisterSignal(String pluginName, String signalName, String[] signalParamTypes);
/**
* Used to emit signal by {@link GodotPlugin} instance.
@@ -366,5 +439,5 @@ public abstract class GodotPlugin {
* @param signalName Name of the signal to emit
* @param signalParams Signal parameters
*/
- private native void nativeEmitSignal(String pluginName, String signalName, Object[] signalParams);
+ private static native void nativeEmitSignal(String pluginName, String signalName, Object[] signalParams);
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java
new file mode 100644
index 0000000000..09366384c2
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java
@@ -0,0 +1,72 @@
+/*************************************************************************/
+/* GodotPluginInfoProvider.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+package org.godotengine.godot.plugin;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Provides the set of information expected from a Godot plugin.
+ */
+public interface GodotPluginInfoProvider {
+ /**
+ * Returns the name of the plugin.
+ */
+ @NonNull
+ String getPluginName();
+
+ /**
+ * Returns the list of signals to be exposed to Godot.
+ */
+ @NonNull
+ default Set<SignalInfo> getPluginSignals() {
+ return Collections.emptySet();
+ }
+
+ /**
+ * Returns the paths for the plugin's gdnative libraries (if any).
+ *
+ * The paths must be relative to the 'assets' directory and point to a '*.gdnlib' file.
+ */
+ @NonNull
+ default Set<String> getPluginGDNativeLibrariesPaths() {
+ return Collections.emptySet();
+ }
+
+ /**
+ * This is invoked on the render thread when the plugin described by this instance has been
+ * registered.
+ */
+ default void onPluginRegistered() {
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
index 1c2d1a6563..5b41205253 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -44,8 +44,6 @@ import androidx.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -56,13 +54,6 @@ public final class GodotPluginRegistry {
private static final String GODOT_PLUGIN_V1_NAME_PREFIX = "org.godotengine.plugin.v1.";
- /**
- * Name for the metadata containing the list of Godot plugins to enable.
- */
- private static final String GODOT_ENABLED_PLUGINS_LABEL = "plugins";
-
- private static final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|";
-
private static GodotPluginRegistry instance;
private final ConcurrentHashMap<String, GodotPlugin> registry;
@@ -132,37 +123,11 @@ public final class GodotPluginRegistry {
return;
}
- // When using the Godot editor for building and exporting the apk, this is used to check
- // which plugins to enable.
- // When using a custom process to generate the apk, the metadata is not needed since
- // it's assumed that the developer is aware of the dependencies included in the apk.
- final Set<String> enabledPluginsSet;
- if (metaData.containsKey(GODOT_ENABLED_PLUGINS_LABEL)) {
- String enabledPlugins = metaData.getString(GODOT_ENABLED_PLUGINS_LABEL, "");
- String[] enabledPluginsList = enabledPlugins.split(PLUGIN_VALUE_SEPARATOR_REGEX);
- if (enabledPluginsList.length == 0) {
- // No plugins to enable. Aborting early.
- return;
- }
-
- enabledPluginsSet = new HashSet<>();
- for (String enabledPlugin : enabledPluginsList) {
- enabledPluginsSet.add(enabledPlugin.trim());
- }
- } else {
- enabledPluginsSet = null;
- }
-
int godotPluginV1NamePrefixLength = GODOT_PLUGIN_V1_NAME_PREFIX.length();
for (String metaDataName : metaData.keySet()) {
// Parse the meta-data looking for entry with the Godot plugin name prefix.
if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) {
String pluginName = metaDataName.substring(godotPluginV1NamePrefixLength).trim();
- if (enabledPluginsSet != null && !enabledPluginsSet.contains(pluginName)) {
- Log.w(TAG, "Plugin " + pluginName + " is listed in the dependencies but is not enabled.");
- continue;
- }
-
Log.i(TAG, "Initializing Godot plugin " + pluginName);
// Retrieve the plugin class full name.
@@ -177,8 +142,7 @@ public final class GodotPluginRegistry {
.getConstructor(Godot.class);
GodotPlugin pluginHandle = pluginConstructor.newInstance(godot);
- // Load the plugin initializer into the registry using the plugin name
- // as key.
+ // Load the plugin initializer into the registry using the plugin name as key.
if (!pluginName.equals(pluginHandle.getPluginName())) {
Log.w(TAG,
"Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName());
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java
index f82c4d3fa0..6428422641 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/UsedByGodot.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/UsedByGodot.java
new file mode 100644
index 0000000000..04c091d944
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/UsedByGodot.java
@@ -0,0 +1,45 @@
+/*************************************************************************/
+/* UsedByGodot.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+package org.godotengine.godot.plugin;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to indicate a method is being invoked from the Godot game logic.
+ *
+ * At runtime, annotated plugin methods are detected and automatically registered.
+ */
+@Target({ ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UsedByGodot {}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java b/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java
index acc9c4981b..2b6e4ad2be 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -39,10 +39,10 @@ public class Crypt {
// Create MD5 Hash
MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
digest.update(input.getBytes());
- byte messageDigest[] = digest.digest();
+ byte[] messageDigest = digest.digest();
// Create Hex String
- StringBuffer hexString = new StringBuffer();
+ StringBuilder hexString = new StringBuilder();
for (int i = 0; i < messageDigest.length; i++)
hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
return hexString.toString();
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java
index 82420eda79..09820fad5f 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -44,7 +44,6 @@ public class GLUtils {
public static final boolean DEBUG = false;
- public static boolean use_32 = false;
public static boolean use_debug_opengl = false;
private static final String[] ATTRIBUTES_NAMES = new String[] {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java
index c89118ad55..721d7c65c6 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
index 7104baf86e..b0ca96271e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt
index 7fa8e3b4e5..b13f9bfeab 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -52,14 +52,13 @@ import org.godotengine.godot.plugin.GodotPluginRegistry
* @see [VkSurfaceView.startRenderer]
*/
internal class VkRenderer {
-
private val pluginRegistry: GodotPluginRegistry = GodotPluginRegistry.getPluginRegistry()
/**
* Called when the surface is created and signals the beginning of rendering.
*/
fun onVkSurfaceCreated(surface: Surface) {
- GodotLib.newcontext(surface, false)
+ GodotLib.newcontext(surface)
for (plugin in pluginRegistry.getAllPlugins()) {
plugin.onVkSurfaceCreated(surface)
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt
index 6b0e12b21a..b01dc2653a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -50,7 +50,6 @@ import android.view.SurfaceView
* </ul>
*/
open internal class VkSurfaceView(context: Context) : SurfaceView(context), SurfaceHolder.Callback {
-
companion object {
fun checkState(expression: Boolean, errorMessage: Any) {
check(expression) { errorMessage.toString() }
@@ -116,7 +115,7 @@ open internal class VkSurfaceView(context: Context) : SurfaceView(context), Surf
/**
* Tear down the rendering thread.
*
- * Must not be called before a [VkRenderer] has been set.
+ * Must not be called before a [VkRenderer] has been set.
*/
fun onDestroy() {
vkThread.blockingExit()
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt
index 7557c8aa22..6e59268076 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -41,7 +41,6 @@ import kotlin.concurrent.withLock
* The implementation is modeled after [android.opengl.GLSurfaceView]'s GLThread.
*/
internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vkRenderer: VkRenderer) : Thread(TAG) {
-
companion object {
private val TAG = VkThread::class.java.simpleName
}
@@ -62,6 +61,7 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
private var rendererInitialized = false
private var rendererResumed = false
private var resumed = false
+ private var surfaceChanged = false
private var hasSurface = false
private var width = 0
private var height = 0
@@ -142,8 +142,10 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
fun onSurfaceChanged(width: Int, height: Int) {
lock.withLock {
hasSurface = true
+ surfaceChanged = true;
this.width = width
this.height = height
+
lockCondition.signalAll()
}
}
@@ -189,8 +191,11 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
rendererInitialized = true
vkRenderer.onVkSurfaceCreated(vkSurfaceView.holder.surface)
}
+ }
+ if (surfaceChanged) {
vkRenderer.onVkSurfaceChanged(vkSurfaceView.holder.surface, width, height)
+ surfaceChanged = false
}
// Break out of the loop so drawing can occur without holding onto the lock.
@@ -226,5 +231,4 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
threadExiting()
}
}
-
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/XRMode.java b/platform/android/java/lib/src/org/godotengine/godot/xr/XRMode.java
index 982e43f9d1..0995477baf 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/XRMode.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/XRMode.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java
index 819bcccdf1..245d573bfc 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java
index 2d9b921466..d3aca267ef 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java
index 43c7f0f966..83aa458064 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java
index 54672db282..341427209b 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java
index 126f3ad5f5..c852e8759a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,7 +30,6 @@
package org.godotengine.godot.xr.regular;
-import org.godotengine.godot.GodotLib;
import org.godotengine.godot.utils.GLUtils;
import android.opengl.GLSurfaceView;
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java
index c83c47bed7..63c5381994 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -38,7 +38,7 @@ import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
-/* Fallback if 32bit View is not supported*/
+/* Fallback if the requested configuration is not supported */
public class RegularFallbackConfigChooser extends RegularConfigChooser {
private static final String TAG = RegularFallbackConfigChooser.class.getSimpleName();
@@ -55,7 +55,6 @@ public class RegularFallbackConfigChooser extends RegularConfigChooser {
if (ec == null) {
Log.w(TAG, "Trying ConfigChooser fallback");
ec = fallback.chooseConfig(egl, display, configs);
- GLUtils.use_32 = false;
}
return ec;
}
diff --git a/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml b/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml
new file mode 100644
index 0000000000..dc180375d5
--- /dev/null
+++ b/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="org.godotengine.godot" />
diff --git a/platform/android/java/lib/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
index d3bdf6a5f2..34925684da 100644
--- a/platform/android/java/lib/CMakeLists.txt
+++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
@@ -1,3 +1,4 @@
+# Non functional cmake build file used to provide Android Studio editor support to the project.
cmake_minimum_required(VERSION 3.6)
project(godot)
diff --git a/platform/android/java/nativeSrcsConfigs/README.md b/platform/android/java/nativeSrcsConfigs/README.md
new file mode 100644
index 0000000000..9d884415cc
--- /dev/null
+++ b/platform/android/java/nativeSrcsConfigs/README.md
@@ -0,0 +1,4 @@
+## Native sources configs
+
+This is a non-functional Android library used to provide Android Studio editor support to the Godot project native files.
+Nothing else should be added to this library.
diff --git a/platform/android/java/nativeSrcsConfigs/build.gradle b/platform/android/java/nativeSrcsConfigs/build.gradle
new file mode 100644
index 0000000000..158bb2b98e
--- /dev/null
+++ b/platform/android/java/nativeSrcsConfigs/build.gradle
@@ -0,0 +1,53 @@
+// Non functional android library used to provide Android Studio editor support to the project.
+plugins {
+ id 'com.android.library'
+}
+
+android {
+ compileSdkVersion versions.compileSdk
+ buildToolsVersion versions.buildTools
+
+ defaultConfig {
+ minSdkVersion versions.minSdk
+ targetSdkVersion versions.targetSdk
+ }
+
+ compileOptions {
+ sourceCompatibility versions.javaVersion
+ targetCompatibility versions.javaVersion
+ }
+
+ packagingOptions {
+ exclude 'META-INF/LICENSE'
+ exclude 'META-INF/NOTICE'
+ }
+
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ }
+ }
+
+ ndkVersion versions.ndkVersion
+
+ externalNativeBuild {
+ cmake {
+ path "CMakeLists.txt"
+ }
+ }
+
+ libraryVariants.all { variant ->
+ def buildType = variant.buildType.name.capitalize()
+
+ def taskPrefix = ""
+ if (project.path != ":") {
+ taskPrefix = project.path + ":"
+ }
+
+ // Disable the externalNativeBuild* task as it would cause build failures since the cmake build
+ // files is only setup for editing support.
+ gradle.startParameter.excludedTaskNames += taskPrefix + "externalNativeBuild" + buildType
+ }
+}
+
+dependencies {}
diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle
index f6921c70aa..584b626900 100644
--- a/platform/android/java/settings.gradle
+++ b/platform/android/java/settings.gradle
@@ -3,3 +3,7 @@ rootProject.name = "Godot"
include ':app'
include ':lib'
+include ':nativeSrcsConfigs'
+
+include ':assetPacks:installTime'
+project(':assetPacks:installTime').projectDir = file("app/assetPacks/installTime")
diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp
index 9b44ac4b41..49891cd739 100644
--- a/platform/android/java_class_wrapper.cpp
+++ b/platform/android/java_class_wrapper.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -37,16 +37,17 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
if (!M)
return false;
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, false);
MethodInfo *method = nullptr;
- for (List<MethodInfo>::Element *E = M->get().front(); E; E = E->next()) {
- if (!p_instance && !E->get()._static) {
+ for (MethodInfo &E : M->get()) {
+ if (!p_instance && !E._static) {
r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
continue;
}
- int pc = E->get().param_types.size();
+ int pc = E.param_types.size();
if (pc > p_argcount) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.argument = pc;
@@ -57,7 +58,7 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
r_error.argument = pc;
continue;
}
- uint32_t *ptypes = E->get().param_types.ptrw();
+ uint32_t *ptypes = E.param_types.ptrw();
bool valid = true;
for (int i = 0; i < pc; i++) {
@@ -101,12 +102,12 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
if (p_args[i]->get_type() != Variant::OBJECT)
arg_expected = Variant::OBJECT;
else {
- Ref<Reference> ref = *p_args[i];
+ Ref<RefCounted> ref = *p_args[i];
if (!ref.is_null()) {
if (Object::cast_to<JavaObject>(ref.ptr())) {
Ref<JavaObject> jo = ref;
//could be faster
- jclass c = env->FindClass(E->get().param_sigs[i].operator String().utf8().get_data());
+ jclass c = env->FindClass(E.param_sigs[i].operator String().utf8().get_data());
if (!c || !env->IsInstanceOf(jo->instance, c)) {
arg_expected = Variant::OBJECT;
} else {
@@ -137,7 +138,7 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
if (!valid)
continue;
- method = &E->get();
+ method = &E;
break;
}
@@ -473,8 +474,8 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
}
- for (List<jobject>::Element *E = to_free.front(); E; E = E->next()) {
- env->DeleteLocalRef(E->get());
+ for (jobject &E : to_free) {
+ env->DeleteLocalRef(E);
}
return success;
@@ -487,7 +488,7 @@ Variant JavaClass::call(const StringName &p_method, const Variant **p_args, int
return ret;
}
- return Reference::call(p_method, p_args, p_argcount, r_error);
+ return RefCounted::call(p_method, p_args, p_argcount, r_error);
}
JavaClass::JavaClass() {
@@ -964,7 +965,8 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
if (class_cache.has(p_class))
return class_cache[p_class];
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, Ref<JavaClass>());
jclass bclass = env->FindClass(p_class.utf8().get_data());
ERR_FAIL_COND_V(!bclass, Ref<JavaClass>());
@@ -1148,7 +1150,8 @@ JavaClassWrapper *JavaClassWrapper::singleton = nullptr;
JavaClassWrapper::JavaClassWrapper(jobject p_activity) {
singleton = this;
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
jclass activityClass = env->FindClass("android/app/Activity");
jmethodID getClassLoader = env->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;");
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 4ccbc6b97e..5cd2c382d2 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -29,12 +29,12 @@
/*************************************************************************/
#include "java_godot_io_wrapper.h"
-#include "core/error_list.h"
+#include "core/error/error_list.h"
// JNIEnv is only valid within the thread it belongs to, in a multi threading environment
// we can't cache it.
// For GodotIO we call all access methods from our thread and we thus get a valid JNIEnv
-// from ThreadAndroid.
+// from get_jni_env().
GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instance) {
godot_io_instance = p_env->NewGlobalRef(p_godot_io_instance);
@@ -48,16 +48,18 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
}
_open_URI = p_env->GetMethodID(cls, "openURI", "(Ljava/lang/String;)I");
+ _get_cache_dir = p_env->GetMethodID(cls, "getCacheDir", "()Ljava/lang/String;");
_get_data_dir = p_env->GetMethodID(cls, "getDataDir", "()Ljava/lang/String;");
_get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;");
_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");
_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I");
+ _screen_get_usable_rect = p_env->GetMethodID(cls, "screenGetUsableRect", "()[I"),
_get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;");
_show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;ZIII)V");
_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I");
- _get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(I)Ljava/lang/String;");
+ _get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(IZ)Ljava/lang/String;");
}
}
@@ -71,7 +73,8 @@ jobject GodotIOJavaWrapper::get_instance() {
Error GodotIOJavaWrapper::open_uri(const String &p_uri) {
if (_open_URI) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, ERR_UNAVAILABLE);
jstring jStr = env->NewStringUTF(p_uri.utf8().get_data());
return env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK;
} else {
@@ -79,9 +82,21 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) {
}
}
+String GodotIOJavaWrapper::get_cache_dir() {
+ if (_get_cache_dir) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String());
+ jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_cache_dir);
+ return jstring_to_string(s, env);
+ } else {
+ return String();
+ }
+}
+
String GodotIOJavaWrapper::get_user_data_dir() {
if (_get_data_dir) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String());
jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir);
return jstring_to_string(s, env);
} else {
@@ -91,7 +106,8 @@ String GodotIOJavaWrapper::get_user_data_dir() {
String GodotIOJavaWrapper::get_locale() {
if (_get_locale) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String());
jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_locale);
return jstring_to_string(s, env);
} else {
@@ -101,7 +117,8 @@ String GodotIOJavaWrapper::get_locale() {
String GodotIOJavaWrapper::get_model() {
if (_get_model) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String());
jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_model);
return jstring_to_string(s, env);
} else {
@@ -111,16 +128,32 @@ String GodotIOJavaWrapper::get_model() {
int GodotIOJavaWrapper::get_screen_dpi() {
if (_get_screen_DPI) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, 160);
return env->CallIntMethod(godot_io_instance, _get_screen_DPI);
} else {
return 160;
}
}
+void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) {
+ if (_screen_get_usable_rect) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+ jintArray returnArray = (jintArray)env->CallObjectMethod(godot_io_instance, _screen_get_usable_rect);
+ ERR_FAIL_COND(env->GetArrayLength(returnArray) != 4);
+ jint *arrayBody = env->GetIntArrayElements(returnArray, JNI_FALSE);
+ for (int i = 0; i < 4; i++) {
+ p_rect_xywh[i] = arrayBody[i];
+ }
+ env->ReleaseIntArrayElements(returnArray, arrayBody, 0);
+ }
+}
+
String GodotIOJavaWrapper::get_unique_id() {
if (_get_unique_id) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String());
jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_unique_id);
return jstring_to_string(s, env);
} else {
@@ -134,7 +167,8 @@ bool GodotIOJavaWrapper::has_vk() {
void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (_show_keyboard) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
jstring jStr = env->NewStringUTF(p_existing.utf8().get_data());
env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
}
@@ -142,45 +176,49 @@ void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int
void GodotIOJavaWrapper::hide_vk() {
if (_hide_keyboard) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
env->CallVoidMethod(godot_io_instance, _hide_keyboard);
}
}
void GodotIOJavaWrapper::set_screen_orientation(int p_orient) {
if (_set_screen_orientation) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
env->CallVoidMethod(godot_io_instance, _set_screen_orientation, p_orient);
}
}
int GodotIOJavaWrapper::get_screen_orientation() {
if (_get_screen_orientation) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, 0);
return env->CallIntMethod(godot_io_instance, _get_screen_orientation);
} else {
return 0;
}
}
-String GodotIOJavaWrapper::get_system_dir(int p_dir) {
+String GodotIOJavaWrapper::get_system_dir(int p_dir, bool p_shared_storage) {
if (_get_system_dir) {
- JNIEnv *env = ThreadAndroid::get_env();
- jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir);
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String("."));
+ jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir, p_shared_storage);
return jstring_to_string(s, env);
} else {
return String(".");
}
}
-// volatile because it can be changed from non-main thread and we need to
+// SafeNumeric because it can be changed from non-main thread and we need to
// ensure the change is immediately visible to other threads.
-static volatile int virtual_keyboard_height;
+static SafeNumeric<int> virtual_keyboard_height;
int GodotIOJavaWrapper::get_vk_height() {
- return virtual_keyboard_height;
+ return virtual_keyboard_height.get();
}
void GodotIOJavaWrapper::set_vk_height(int p_height) {
- virtual_keyboard_height = p_height;
+ virtual_keyboard_height.set(p_height);
}
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index 6465ded985..8f6d7f813f 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -46,10 +46,12 @@ private:
jclass cls;
jmethodID _open_URI = 0;
+ jmethodID _get_cache_dir = 0;
jmethodID _get_data_dir = 0;
jmethodID _get_locale = 0;
jmethodID _get_model = 0;
jmethodID _get_screen_DPI = 0;
+ jmethodID _screen_get_usable_rect = 0;
jmethodID _get_unique_id = 0;
jmethodID _show_keyboard = 0;
jmethodID _hide_keyboard = 0;
@@ -64,10 +66,12 @@ public:
jobject get_instance();
Error open_uri(const String &p_uri);
+ String get_cache_dir();
String get_user_data_dir();
String get_locale();
String get_model();
int get_screen_dpi();
+ void screen_get_usable_rect(int (&p_rect_xywh)[4]);
String get_unique_id();
bool has_vk();
void show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end);
@@ -76,7 +80,7 @@ public:
void set_vk_height(int p_height);
void set_screen_orientation(int p_orient);
int get_screen_orientation();
- String get_system_dir(int p_dir);
+ String get_system_dir(int p_dir, bool p_shared_storage);
};
#endif /* !JAVA_GODOT_IO_WRAPPER_H */
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 4610b94363..3236512f5c 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -34,16 +34,15 @@
#include "java_godot_wrapper.h"
#include "android/asset_manager_jni.h"
+#include "android_input_handler.h"
#include "api/java_class_wrapper.h"
#include "api/jni_singleton.h"
-#include "audio_driver_jandroid.h"
-#include "core/engine.h"
+#include "core/config/engine.h"
+#include "core/config/project_settings.h"
#include "core/input/input.h"
-#include "core/project_settings.h"
#include "dir_access_jandroid.h"
#include "display_server_android.h"
#include "file_access_android.h"
-#include "file_access_jandroid.h"
#include "jni_utils.h"
#include "main/main.h"
#include "net_socket_android.h"
@@ -51,17 +50,19 @@
#include "string_android.h"
#include "thread_jandroid.h"
+#include <android/input.h>
#include <unistd.h>
#include <android/native_window_jni.h>
static JavaClassWrapper *java_class_wrapper = nullptr;
static OS_Android *os_android = nullptr;
+static AndroidInputHandler *input_handler = nullptr;
static GodotJavaWrapper *godot_java = nullptr;
static GodotIOJavaWrapper *godot_io_java = nullptr;
static bool initialized = false;
-static int step = 0;
+static SafeNumeric<int> step; // Shared between UI and render threads
static Size2 new_size;
static Vector3 accelerometer;
@@ -87,18 +88,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en
godot_java = new GodotJavaWrapper(env, activity, godot_instance);
godot_io_java = new GodotIOJavaWrapper(env, godot_java->get_member_object("io", "Lorg/godotengine/godot/GodotIO;", env));
- ThreadAndroid::make_default(jvm);
-#ifdef USE_JAVA_FILE_ACCESS
- FileAccessJAndroid::setup(godot_io_java->get_instance());
-#else
+ init_thread_jandroid(jvm, env);
jobject amgr = env->NewGlobalRef(p_asset_manager);
FileAccessAndroid::asset_manager = AAssetManager_fromJava(env, amgr);
-#endif
DirAccessJAndroid::setup(godot_io_java->get_instance());
- AudioDriverAndroid::setup(godot_io_java->get_instance());
NetSocketAndroid::setup(godot_java->get_member_object("netUtils", "Lorg/godotengine/godot/utils/GodotNetUtils;", env));
os_android = new OS_Android(godot_java, godot_io_java, p_use_apk_expansion);
@@ -117,13 +113,16 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env
if (godot_java) {
delete godot_java;
}
+ if (input_handler) {
+ delete input_handler;
+ }
if (os_android) {
delete os_android;
}
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline) {
- ThreadAndroid::setup_thread();
+ setup_android_thread();
const char **cmdline = nullptr;
jstring *j_cmdline = nullptr;
@@ -131,9 +130,11 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc
if (p_cmdline) {
cmdlen = env->GetArrayLength(p_cmdline);
if (cmdlen) {
- cmdline = (const char **)malloc((cmdlen + 1) * sizeof(const char *));
+ cmdline = (const char **)memalloc((cmdlen + 1) * sizeof(const char *));
+ ERR_FAIL_NULL_MSG(cmdline, "Out of memory.");
cmdline[cmdlen] = nullptr;
- j_cmdline = (jstring *)malloc(cmdlen * sizeof(jstring));
+ j_cmdline = (jstring *)memalloc(cmdlen * sizeof(jstring));
+ ERR_FAIL_NULL_MSG(j_cmdline, "Out of memory.");
for (int i = 0; i < cmdlen; i++) {
jstring string = (jstring)env->GetObjectArrayElement(p_cmdline, i);
@@ -151,17 +152,17 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc
for (int i = 0; i < cmdlen; ++i) {
env->ReleaseStringUTFChars(j_cmdline[i], cmdline[i]);
}
- free(j_cmdline);
+ memfree(j_cmdline);
}
- free(cmdline);
+ memfree(cmdline);
}
if (err != OK) {
- return; //should exit instead and print the error
+ return; // should exit instead and print the error
}
java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity()));
- ClassDB::register_class<JNISingleton>();
+ GDREGISTER_CLASS(JNISingleton);
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height) {
@@ -169,62 +170,66 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, j
os_android->set_display_size(Size2i(p_width, p_height));
// No need to reset the surface during startup
- if (step > 0) {
+ if (step.get() > 0) {
if (p_surface) {
ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface);
os_android->set_native_window(native_window);
DisplayServerAndroid::get_singleton()->reset_window();
+ DisplayServerAndroid::get_singleton()->notify_surface_changed(p_width, p_height);
}
}
}
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface, jboolean p_32_bits) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface) {
if (os_android) {
- if (step == 0) {
+ if (step.get() == 0) {
// During startup
- os_android->set_context_is_16_bits(!p_32_bits);
if (p_surface) {
ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface);
os_android->set_native_window(native_window);
}
} else {
// Rendering context recreated because it was lost; restart app to let it reload everything
+ step.set(-1); // Ensure no further steps are attempted and no further events are sent
os_android->main_loop_end();
godot_java->restart(env);
- step = -1; // Ensure no further steps are attempted
}
}
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz) {
- if (step == 0)
+ if (step.get() == 0)
return;
- os_android->main_loop_request_go_back();
+ if (DisplayServerAndroid *dsa = Object::cast_to<DisplayServerAndroid>(DisplayServer::get_singleton())) {
+ dsa->send_window_event(DisplayServer::WINDOW_EVENT_GO_BACK_REQUEST);
+ }
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) {
- if (step == -1)
+ if (step.get() == -1)
return;
- if (step == 0) {
- // Since Godot is initialized on the UI thread, _main_thread_id was set to that thread's id,
+ if (step.get() == 0) {
+ // Since Godot is initialized on the UI thread, main_thread_id was set to that thread's id,
// but for Godot purposes, the main thread is the one running the game loop
Main::setup2(Thread::get_caller_id());
- ++step;
+ input_handler = new AndroidInputHandler();
+ step.increment();
return;
}
- if (step == 1) {
+ if (step.get() == 1) {
if (!Main::start()) {
- return; //should exit instead and print the error
+ return; // should exit instead and print the error
}
+ godot_java->on_godot_setup_completed(env);
os_android->main_loop_begin();
godot_java->on_godot_main_loop_started(env);
- ++step;
+ step.increment();
}
DisplayServerAndroid::get_singleton()->process_accelerometer(accelerometer);
@@ -237,100 +242,124 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jcl
}
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jclass clazz, jint ev, jint pointer, jint count, jintArray positions) {
- if (step == 0)
+void touch_preprocessing(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor) {
+ if (step.get() <= 0)
return;
- Vector<DisplayServerAndroid::TouchPos> points;
- for (int i = 0; i < count; i++) {
- jint p[3];
- env->GetIntArrayRegion(positions, i * 3, 3, p);
- DisplayServerAndroid::TouchPos tp;
+ Vector<AndroidInputHandler::TouchPos> points;
+ for (int i = 0; i < pointer_count; i++) {
+ jfloat p[3];
+ env->GetFloatArrayRegion(positions, i * 3, 3, p);
+ AndroidInputHandler::TouchPos tp;
tp.pos = Point2(p[1], p[2]);
- tp.id = p[0];
+ tp.id = (int)p[0];
points.push_back(tp);
}
+ if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE || (input_device & AINPUT_SOURCE_MOUSE_RELATIVE) == AINPUT_SOURCE_MOUSE_RELATIVE) {
+ input_handler->process_mouse_event(input_device, ev, buttons_mask, points[0].pos, vertical_factor, horizontal_factor);
+ } else {
+ input_handler->process_touch(ev, pointer, points);
+ }
+}
+
+// Called on the UI thread
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3F(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray position) {
+ touch_preprocessing(env, clazz, input_device, ev, pointer, pointer_count, position);
+}
- DisplayServerAndroid::get_singleton()->process_touch(ev, pointer, points);
+// Called on the UI thread
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FI(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray position, jint buttons_mask) {
+ touch_preprocessing(env, clazz, input_device, ev, pointer, pointer_count, position, buttons_mask);
+}
- /*
- if (os_android)
- os_android->process_touch(ev,pointer,points);
- */
+// Called on the UI thread
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FIFF(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray position, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor) {
+ touch_preprocessing(env, clazz, input_device, ev, pointer, pointer_count, position, buttons_mask, vertical_factor, horizontal_factor);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jint p_x, jint p_y) {
- if (step == 0)
+// Called on the UI thread
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jfloat p_x, jfloat p_y) {
+ if (step.get() <= 0)
return;
- DisplayServerAndroid::get_singleton()->process_hover(p_type, Point2(p_x, p_y));
+ input_handler->process_hover(p_type, Point2(p_x, p_y));
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubletap(JNIEnv *env, jclass clazz, jint p_x, jint p_y) {
- if (step == 0)
+// Called on the UI thread
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubleTap(JNIEnv *env, jclass clazz, jint p_button_mask, jint p_x, jint p_y) {
+ if (step.get() <= 0)
return;
- DisplayServerAndroid::get_singleton()->process_double_tap(Point2(p_x, p_y));
+ input_handler->process_double_tap(p_button_mask, Point2(p_x, p_y));
}
+// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jclass clazz, jint p_x, jint p_y) {
- if (step == 0)
+ if (step.get() <= 0)
return;
- DisplayServerAndroid::get_singleton()->process_scroll(Point2(p_x, p_y));
+ input_handler->process_scroll(Point2(p_x, p_y));
}
+// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed) {
- if (step == 0)
+ if (step.get() <= 0)
return;
- DisplayServerAndroid::JoypadEvent jevent;
+ AndroidInputHandler::JoypadEvent jevent;
jevent.device = p_device;
- jevent.type = DisplayServerAndroid::JOY_EVENT_BUTTON;
+ jevent.type = AndroidInputHandler::JOY_EVENT_BUTTON;
jevent.index = p_button;
jevent.pressed = p_pressed;
- DisplayServerAndroid::get_singleton()->process_joy_event(jevent);
+ input_handler->process_joy_event(jevent);
}
+// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jclass clazz, jint p_device, jint p_axis, jfloat p_value) {
- if (step == 0)
+ if (step.get() <= 0) {
return;
+ }
- DisplayServerAndroid::JoypadEvent jevent;
+ AndroidInputHandler::JoypadEvent jevent;
jevent.device = p_device;
- jevent.type = DisplayServerAndroid::JOY_EVENT_AXIS;
+ jevent.type = AndroidInputHandler::JOY_EVENT_AXIS;
jevent.index = p_axis;
jevent.value = p_value;
- DisplayServerAndroid::get_singleton()->process_joy_event(jevent);
+ input_handler->process_joy_event(jevent);
}
+// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jclass clazz, jint p_device, jint p_hat_x, jint p_hat_y) {
- if (step == 0)
+ if (step.get() <= 0) {
return;
+ }
- DisplayServerAndroid::JoypadEvent jevent;
+ AndroidInputHandler::JoypadEvent jevent;
jevent.device = p_device;
- jevent.type = DisplayServerAndroid::JOY_EVENT_HAT;
- int hat = 0;
+ jevent.type = AndroidInputHandler::JOY_EVENT_HAT;
+ HatMask hat = HatMask::CENTER;
if (p_hat_x != 0) {
- if (p_hat_x < 0)
- hat |= Input::HAT_MASK_LEFT;
- else
- hat |= Input::HAT_MASK_RIGHT;
+ if (p_hat_x < 0) {
+ hat |= HatMask::LEFT;
+ } else {
+ hat |= HatMask::RIGHT;
+ }
}
if (p_hat_y != 0) {
- if (p_hat_y < 0)
- hat |= Input::HAT_MASK_UP;
- else
- hat |= Input::HAT_MASK_DOWN;
+ if (p_hat_y < 0) {
+ hat |= HatMask::UP;
+ } else {
+ hat |= HatMask::DOWN;
+ }
}
jevent.hat = hat;
- DisplayServerAndroid::get_singleton()->process_joy_event(jevent);
+ input_handler->process_joy_event(jevent);
}
+// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jclass clazz, jint p_device, jboolean p_connected, jstring p_name) {
if (os_android) {
String name = jstring_to_string(p_name, env);
@@ -338,11 +367,12 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(
}
}
+// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_keycode, jint p_scancode, jint p_unicode_char, jboolean p_pressed) {
- if (step == 0)
+ if (step.get() <= 0)
return;
- DisplayServerAndroid::get_singleton()->process_key_event(p_keycode, p_scancode, p_unicode_char, p_pressed);
+ input_handler->process_key_event(p_keycode, p_scancode, p_unicode_char, p_pressed);
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) {
@@ -362,24 +392,19 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz) {
- if (step == 0)
+ if (step.get() <= 0)
return;
os_android->main_loop_focusin();
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz) {
- if (step == 0)
+ if (step.get() <= 0)
return;
os_android->main_loop_focusout();
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jclass clazz) {
- ThreadAndroid::setup_thread();
- AudioDriverAndroid::thread_func(env);
-}
-
JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path) {
String js = jstring_to_string(path, env);
@@ -435,8 +460,8 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *
env->DeleteLocalRef(obj);
};
- static_assert(VARIANT_ARG_MAX == 5, "This code needs to be updated if VARIANT_ARG_MAX != 5");
- obj->call_deferred(str_method, args[0], args[1], args[2], args[3], args[4]);
+ static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8");
+ obj->call_deferred(str_method, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
// something
env->PopLocalFrame(nullptr);
}
@@ -448,12 +473,12 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu
}
if (os_android->get_main_loop()) {
- os_android->get_main_loop()->emit_signal("on_request_permissions_result", permission, p_result == JNI_TRUE);
+ os_android->get_main_loop()->emit_signal(SNAME("on_request_permissions_result"), permission, p_result == JNI_TRUE);
}
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz) {
- if (step == 0)
+ if (step.get() <= 0)
return;
if (os_android->get_main_loop()) {
@@ -462,7 +487,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNI
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz) {
- if (step == 0)
+ if (step.get() <= 0)
return;
if (os_android->get_main_loop()) {
diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h
index 07584518e5..7ea74480cb 100644
--- a/platform/android/java_godot_lib_jni.h
+++ b/platform/android/java_godot_lib_jni.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -41,19 +41,21 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface, jboolean p_32_bits);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jclass clazz, jint ev, jint pointer, jint count, jintArray positions);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jint p_x, jint p_y);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubletap(JNIEnv *env, jclass clazz, jint p_x, jint p_y);
+void touch_preprocessing(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask = 0, jfloat vertical_factor = 0, jfloat horizontal_factor = 0);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3F(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FI(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FIFF(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jfloat p_x, jfloat p_y);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubleTap(JNIEnv *env, jclass clazz, jint p_button_mask, jint p_x, jint p_y);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jclass clazz, jint p_x, jint p_y);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_keycode, jint p_scancode, jint p_unicode_char, jboolean p_pressed);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jclass clazz, jint p_device, jint p_axis, jfloat p_value);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jclass clazz, jint p_device, jint p_hat_x, jint p_hat_y);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jclass clazz, jint p_device, jboolean p_connected, jstring p_name);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gravity(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z);
diff --git a/platform/android/file_access_jandroid.h b/platform/android/java_godot_view_wrapper.cpp
index e252a4d3ac..837d2aeced 100644
--- a/platform/android/file_access_jandroid.h
+++ b/platform/android/java_godot_view_wrapper.cpp
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* file_access_jandroid.h */
+/* java_godot_view_wrapper.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,56 +28,56 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef FILE_ACCESS_JANDROID_H
-#define FILE_ACCESS_JANDROID_H
+#include "java_godot_view_wrapper.h"
-#include "core/os/file_access.h"
-#include "java_godot_lib_jni.h"
-class FileAccessJAndroid : public FileAccess {
- static jobject io;
- static jclass cls;
+#include "thread_jandroid.h"
- static jmethodID _file_open;
- static jmethodID _file_get_size;
- static jmethodID _file_seek;
- static jmethodID _file_tell;
- static jmethodID _file_eof;
- static jmethodID _file_read;
- static jmethodID _file_close;
+GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
- int id;
- static FileAccess *create_jandroid();
+ _godot_view = env->NewGlobalRef(godot_view);
-public:
- virtual Error _open(const String &p_path, int p_mode_flags); ///< open a file
- virtual void close(); ///< close a file
- virtual bool is_open() const; ///< true when file is open
+ _cls = (jclass)env->NewGlobalRef(env->GetObjectClass(godot_view));
- virtual void seek(size_t p_position); ///< seek to a given position
- virtual void seek_end(int64_t p_position = 0); ///< seek from the end of file
- virtual size_t get_position() const; ///< get position in the file
- virtual size_t get_len() const; ///< get size of the file
+ if (android_get_device_api_level() >= __ANDROID_API_O__) {
+ _request_pointer_capture = env->GetMethodID(_cls, "requestPointerCapture", "()V");
+ _release_pointer_capture = env->GetMethodID(_cls, "releasePointerCapture", "()V");
+ _set_pointer_icon = env->GetMethodID(_cls, "setPointerIcon", "(I)V");
+ }
+}
- virtual bool eof_reached() const; ///< reading passed EOF
+void GodotJavaViewWrapper::request_pointer_capture() {
+ if (_request_pointer_capture != 0) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
- virtual uint8_t get_8() const; ///< get a byte
- virtual int get_buffer(uint8_t *p_dst, int p_length) const;
+ env->CallVoidMethod(_godot_view, _request_pointer_capture);
+ }
+}
- virtual Error get_error() const; ///< get last error
+void GodotJavaViewWrapper::release_pointer_capture() {
+ if (_request_pointer_capture != 0) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
- virtual void flush();
- virtual void store_8(uint8_t p_dest); ///< store a byte
+ env->CallVoidMethod(_godot_view, _release_pointer_capture);
+ }
+}
- virtual bool file_exists(const String &p_path); ///< return true if a file exists
+void GodotJavaViewWrapper::set_pointer_icon(int pointer_type) {
+ if (_set_pointer_icon != 0) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
- static void setup(jobject p_io);
+ env->CallVoidMethod(_godot_view, _set_pointer_icon, pointer_type);
+ }
+}
- virtual uint64_t _get_modified_time(const String &p_file) { return 0; }
- virtual uint32_t _get_unix_permissions(const String &p_file) { return 0; }
- virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) { return FAILED; }
+GodotJavaViewWrapper::~GodotJavaViewWrapper() {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
- FileAccessJAndroid();
- ~FileAccessJAndroid();
-};
-
-#endif // FILE_ACCESS_JANDROID_H
+ env->DeleteGlobalRef(_godot_view);
+ env->DeleteGlobalRef(_cls);
+}
diff --git a/platform/android/audio_driver_jandroid.h b/platform/android/java_godot_view_wrapper.h
index 953ade9311..da547d8118 100644
--- a/platform/android/audio_driver_jandroid.h
+++ b/platform/android/java_godot_view_wrapper.h
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* audio_driver_jandroid.h */
+/* java_godot_view_wrapper.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,51 +28,33 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef AUDIO_DRIVER_ANDROID_H
-#define AUDIO_DRIVER_ANDROID_H
+#ifndef GODOT_JAVA_GODOT_VIEW_WRAPPER_H
+#define GODOT_JAVA_GODOT_VIEW_WRAPPER_H
-#include "servers/audio_server.h"
+#include <android/log.h>
+#include <jni.h>
-#include "java_godot_lib_jni.h"
+#include "string_android.h"
-class AudioDriverAndroid : public AudioDriver {
- static Mutex mutex;
- static AudioDriverAndroid *s_ad;
- static jobject io;
- static jmethodID _init_audio;
- static jmethodID _write_buffer;
- static jmethodID _quit;
- static jmethodID _pause;
- static bool active;
- static bool quit;
+// Class that makes functions in java/src/org/godotengine/godot/GodotView.java callable from C++
+class GodotJavaViewWrapper {
+private:
+ jclass _cls;
- static jclass cls;
+ jobject _godot_view;
- static jobject audioBuffer;
- static void *audioBufferPinned;
- static int32_t *audioBuffer32;
- static int audioBufferFrames;
- static int mix_rate;
+ jmethodID _request_pointer_capture = 0;
+ jmethodID _release_pointer_capture = 0;
+ jmethodID _set_pointer_icon = 0;
public:
- void set_singleton();
+ GodotJavaViewWrapper(jobject godot_view);
- virtual const char *get_name() const;
+ void request_pointer_capture();
+ void release_pointer_capture();
+ void set_pointer_icon(int pointer_type);
- virtual Error init();
- virtual void start();
- virtual int get_mix_rate() const;
- virtual SpeakerMode get_speaker_mode() const;
- virtual void lock();
- virtual void unlock();
- virtual void finish();
-
- virtual void set_pause(bool p_pause);
-
- static void setup(jobject p_io);
- static void thread_func(JNIEnv *env);
-
- AudioDriverAndroid();
+ ~GodotJavaViewWrapper();
};
-#endif // AUDIO_DRIVER_ANDROID_H
+#endif //GODOT_JAVA_GODOT_VIEW_WRAPPER_H
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index cff591d903..bfd93345f3 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -33,7 +33,7 @@
// JNIEnv is only valid within the thread it belongs to, in a multi threading environment
// we can't cache it.
// For Godot we call most access methods from our thread and we thus get a valid JNIEnv
-// from ThreadAndroid. For one or two we expect to pass the environment
+// from get_jni_env(). For one or two we expect to pass the environment
// TODO we could probably create a base class for this...
@@ -74,6 +74,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_is_activity_resumed = p_env->GetMethodID(godot_class, "isActivityResumed", "()Z");
_vibrate = p_env->GetMethodID(godot_class, "vibrate", "(I)V");
_get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;");
+ _on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V");
_on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V");
// get some Activity method pointers...
@@ -91,7 +92,9 @@ jobject GodotJavaWrapper::get_activity() {
jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env) {
if (godot_class) {
if (p_env == nullptr)
- p_env = ThreadAndroid::get_env();
+ p_env = get_jni_env();
+
+ ERR_FAIL_COND_V(p_env == nullptr, nullptr);
jfieldID fid = p_env->GetStaticFieldID(godot_class, p_name, p_class);
return p_env->GetStaticObjectField(godot_class, fid);
@@ -102,56 +105,90 @@ jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_cl
jobject GodotJavaWrapper::get_class_loader() {
if (_get_class_loader) {
- JNIEnv *env = ThreadAndroid::get_env();
- return env->CallObjectMethod(godot_instance, _get_class_loader);
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, nullptr);
+
+ return env->CallObjectMethod(activity, _get_class_loader);
} else {
return nullptr;
}
}
+GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() {
+ if (_godot_view != nullptr) {
+ return _godot_view;
+ }
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, nullptr);
+
+ jmethodID godot_view_getter = env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;");
+ _godot_view = new GodotJavaViewWrapper(env->CallObjectMethod(godot_instance, godot_view_getter));
+ return _godot_view;
+}
+
void GodotJavaWrapper::on_video_init(JNIEnv *p_env) {
- if (_on_video_init)
+ if (_on_video_init) {
if (p_env == nullptr)
- p_env = ThreadAndroid::get_env();
+ p_env = get_jni_env();
+ ERR_FAIL_COND(p_env == nullptr);
+
+ p_env->CallVoidMethod(godot_instance, _on_video_init);
+ }
+}
- p_env->CallVoidMethod(godot_instance, _on_video_init);
+void GodotJavaWrapper::on_godot_setup_completed(JNIEnv *p_env) {
+ if (_on_godot_setup_completed) {
+ if (p_env == nullptr) {
+ p_env = get_jni_env();
+ }
+ p_env->CallVoidMethod(godot_instance, _on_godot_setup_completed);
+ }
}
void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) {
if (_on_godot_main_loop_started) {
if (p_env == nullptr) {
- p_env = ThreadAndroid::get_env();
+ p_env = get_jni_env();
}
+ ERR_FAIL_COND(p_env == nullptr);
+ p_env->CallVoidMethod(godot_instance, _on_godot_main_loop_started);
}
- p_env->CallVoidMethod(godot_instance, _on_godot_main_loop_started);
}
void GodotJavaWrapper::restart(JNIEnv *p_env) {
- if (_restart)
+ if (_restart) {
if (p_env == nullptr)
- p_env = ThreadAndroid::get_env();
+ p_env = get_jni_env();
+ ERR_FAIL_COND(p_env == nullptr);
- p_env->CallVoidMethod(godot_instance, _restart);
+ p_env->CallVoidMethod(godot_instance, _restart);
+ }
}
void GodotJavaWrapper::force_quit(JNIEnv *p_env) {
- if (_finish)
+ if (_finish) {
if (p_env == nullptr)
- p_env = ThreadAndroid::get_env();
+ p_env = get_jni_env();
+ ERR_FAIL_COND(p_env == nullptr);
- p_env->CallVoidMethod(godot_instance, _finish);
+ p_env->CallVoidMethod(godot_instance, _finish);
+ }
}
void GodotJavaWrapper::set_keep_screen_on(bool p_enabled) {
if (_set_keep_screen_on) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
env->CallVoidMethod(godot_instance, _set_keep_screen_on, p_enabled);
}
}
void GodotJavaWrapper::alert(const String &p_message, const String &p_title) {
if (_alert) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
jstring jStrMessage = env->NewStringUTF(p_message.utf8().get_data());
jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data());
env->CallVoidMethod(godot_instance, _alert, jStrMessage, jStrTitle);
@@ -159,7 +196,9 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) {
}
int GodotJavaWrapper::get_gles_version_code() {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, 0);
+
if (_get_GLES_version_code) {
return env->CallIntMethod(godot_instance, _get_GLES_version_code);
}
@@ -173,7 +212,9 @@ bool GodotJavaWrapper::has_get_clipboard() {
String GodotJavaWrapper::get_clipboard() {
if (_get_clipboard) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String());
+
jstring s = (jstring)env->CallObjectMethod(godot_instance, _get_clipboard);
return jstring_to_string(s, env);
} else {
@@ -183,7 +224,9 @@ String GodotJavaWrapper::get_clipboard() {
String GodotJavaWrapper::get_input_fallback_mapping() {
if (_get_input_fallback_mapping) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, String());
+
jstring fallback_mapping = (jstring)env->CallObjectMethod(godot_instance, _get_input_fallback_mapping);
return jstring_to_string(fallback_mapping, env);
} else {
@@ -197,7 +240,9 @@ bool GodotJavaWrapper::has_set_clipboard() {
void GodotJavaWrapper::set_clipboard(const String &p_text) {
if (_set_clipboard) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
jstring jStr = env->NewStringUTF(p_text.utf8().get_data());
env->CallVoidMethod(godot_instance, _set_clipboard, jStr);
}
@@ -205,7 +250,9 @@ void GodotJavaWrapper::set_clipboard(const String &p_text) {
bool GodotJavaWrapper::request_permission(const String &p_name) {
if (_request_permission) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, false);
+
jstring jStrName = env->NewStringUTF(p_name.utf8().get_data());
return env->CallBooleanMethod(godot_instance, _request_permission, jStrName);
} else {
@@ -215,7 +262,9 @@ bool GodotJavaWrapper::request_permission(const String &p_name) {
bool GodotJavaWrapper::request_permissions() {
if (_request_permissions) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, false);
+
return env->CallBooleanMethod(godot_instance, _request_permissions);
} else {
return false;
@@ -225,7 +274,9 @@ bool GodotJavaWrapper::request_permissions() {
Vector<String> GodotJavaWrapper::get_granted_permissions() const {
Vector<String> permissions_list;
if (_get_granted_permissions) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, permissions_list);
+
jobject permissions_object = env->CallObjectMethod(godot_instance, _get_granted_permissions);
jobjectArray *arr = reinterpret_cast<jobjectArray *>(&permissions_object);
@@ -243,14 +294,18 @@ Vector<String> GodotJavaWrapper::get_granted_permissions() const {
void GodotJavaWrapper::init_input_devices() {
if (_init_input_devices) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
env->CallVoidMethod(godot_instance, _init_input_devices);
}
}
jobject GodotJavaWrapper::get_surface() {
if (_get_surface) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, nullptr);
+
return env->CallObjectMethod(godot_instance, _get_surface);
} else {
return nullptr;
@@ -259,7 +314,9 @@ jobject GodotJavaWrapper::get_surface() {
bool GodotJavaWrapper::is_activity_resumed() {
if (_is_activity_resumed) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, false);
+
return env->CallBooleanMethod(godot_instance, _is_activity_resumed);
} else {
return false;
@@ -268,7 +325,9 @@ bool GodotJavaWrapper::is_activity_resumed() {
void GodotJavaWrapper::vibrate(int p_duration_ms) {
if (_vibrate) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
env->CallVoidMethod(godot_instance, _vibrate, p_duration_ms);
}
}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index e0c3809a64..0e20747a16 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -37,6 +37,7 @@
#include <android/log.h>
#include <jni.h>
+#include "java_godot_view_wrapper.h"
#include "string_android.h"
// Class that makes functions in java/src/org/godotengine/godot/Godot.java callable from C++
@@ -47,6 +48,8 @@ private:
jclass godot_class;
jclass activity_class;
+ GodotJavaViewWrapper *_godot_view = nullptr;
+
jmethodID _on_video_init = 0;
jmethodID _restart = 0;
jmethodID _finish = 0;
@@ -63,6 +66,7 @@ private:
jmethodID _is_activity_resumed = 0;
jmethodID _vibrate = 0;
jmethodID _get_input_fallback_mapping = 0;
+ jmethodID _on_godot_setup_completed = 0;
jmethodID _on_godot_main_loop_started = 0;
jmethodID _get_class_loader = 0;
@@ -74,8 +78,10 @@ public:
jobject get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env = nullptr);
jobject get_class_loader();
+ GodotJavaViewWrapper *get_godot_view();
void on_video_init(JNIEnv *p_env = nullptr);
+ void on_godot_setup_completed(JNIEnv *p_env = nullptr);
void on_godot_main_loop_started(JNIEnv *p_env = nullptr);
void restart(JNIEnv *p_env = nullptr);
void force_quit(JNIEnv *p_env = nullptr);
diff --git a/platform/android/jni_utils.cpp b/platform/android/jni_utils.cpp
index 8e1ae53b78..94652ab7ac 100644
--- a/platform/android/jni_utils.cpp
+++ b/platform/android/jni_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/jni_utils.h b/platform/android/jni_utils.h
index 5320715853..f2b89392b5 100644
--- a/platform/android/jni_utils.h
+++ b/platform/android/jni_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -32,8 +32,8 @@
#define JNI_UTILS_H
#include "string_android.h"
-#include <core/engine.h>
-#include <core/variant.h>
+#include <core/config/engine.h>
+#include <core/variant/variant.h>
#include <jni.h>
struct jvalret {
diff --git a/platform/android/logo.png b/platform/android/logo.png
index f44d360a25..9c8be93646 100644
--- a/platform/android/logo.png
+++ b/platform/android/logo.png
Binary files differ
diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp
index 0341ef3ec6..dbd1870ee6 100644
--- a/platform/android/net_socket_android.cpp
+++ b/platform/android/net_socket_android.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -38,7 +38,7 @@ jmethodID NetSocketAndroid::_multicast_lock_acquire = 0;
jmethodID NetSocketAndroid::_multicast_lock_release = 0;
void NetSocketAndroid::setup(jobject p_net_utils) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
net_utils = env->NewGlobalRef(p_net_utils);
@@ -51,14 +51,14 @@ void NetSocketAndroid::setup(jobject p_net_utils) {
void NetSocketAndroid::multicast_lock_acquire() {
if (_multicast_lock_acquire) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
env->CallVoidMethod(net_utils, _multicast_lock_acquire);
}
}
void NetSocketAndroid::multicast_lock_release() {
if (_multicast_lock_release) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
env->CallVoidMethod(net_utils, _multicast_lock_release);
}
}
@@ -103,7 +103,7 @@ Error NetSocketAndroid::set_broadcasting_enabled(bool p_enabled) {
return OK;
}
-Error NetSocketAndroid::join_multicast_group(const IP_Address &p_multi_address, String p_if_name) {
+Error NetSocketAndroid::join_multicast_group(const IPAddress &p_multi_address, String p_if_name) {
Error err = NetSocketPosix::join_multicast_group(p_multi_address, p_if_name);
if (err != OK)
return err;
@@ -115,7 +115,7 @@ Error NetSocketAndroid::join_multicast_group(const IP_Address &p_multi_address,
return OK;
}
-Error NetSocketAndroid::leave_multicast_group(const IP_Address &p_multi_address, String p_if_name) {
+Error NetSocketAndroid::leave_multicast_group(const IPAddress &p_multi_address, String p_if_name) {
Error err = NetSocketPosix::leave_multicast_group(p_multi_address, p_if_name);
if (err != OK)
return err;
diff --git a/platform/android/net_socket_android.h b/platform/android/net_socket_android.h
index 955d906535..60090c26bb 100644
--- a/platform/android/net_socket_android.h
+++ b/platform/android/net_socket_android.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -67,8 +67,8 @@ public:
virtual void close();
virtual Error set_broadcasting_enabled(bool p_enabled);
- virtual Error join_multicast_group(const IP_Address &p_multi_address, String p_if_name);
- virtual Error leave_multicast_group(const IP_Address &p_multi_address, String p_if_name);
+ virtual Error join_multicast_group(const IPAddress &p_multi_address, String p_if_name);
+ virtual Error leave_multicast_group(const IPAddress &p_multi_address, String p_if_name);
NetSocketAndroid() {}
~NetSocketAndroid();
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index baf6ee952a..0e5e10bc0a 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,16 +30,14 @@
#include "os_android.h"
-#include "core/io/file_access_buffered_fa.h"
-#include "core/project_settings.h"
+#include "core/config/project_settings.h"
#include "drivers/unix/dir_access_unix.h"
#include "drivers/unix/file_access_unix.h"
-#include "file_access_android.h"
#include "main/main.h"
#include "platform/android/display_server_android.h"
#include "dir_access_jandroid.h"
-#include "file_access_jandroid.h"
+#include "file_access_android.h"
#include "net_socket_android.h"
#include <dlfcn.h>
@@ -47,6 +45,23 @@
#include "java_godot_io_wrapper.h"
#include "java_godot_wrapper.h"
+String _remove_symlink(const String &dir) {
+ // Workaround for Android 6.0+ using a symlink.
+ // Save the current directory.
+ char current_dir_name[2048];
+ getcwd(current_dir_name, 2048);
+ // Change directory to the external data directory.
+ chdir(dir.utf8().get_data());
+ // Get the actual directory without the potential symlink.
+ char dir_name_wihout_symlink[2048];
+ getcwd(dir_name_wihout_symlink, 2048);
+ // Convert back to a String.
+ String dir_without_symlink(dir_name_wihout_symlink);
+ // Restore original current directory.
+ chdir(current_dir_name);
+ return dir_without_symlink;
+}
+
class AndroidLogger : public Logger {
public:
virtual void logv(const char *p_format, va_list p_list, bool p_err) {
@@ -56,22 +71,23 @@ public:
virtual ~AndroidLogger() {}
};
+void OS_Android::alert(const String &p_alert, const String &p_title) {
+ GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+ ERR_FAIL_COND(!godot_java);
+
+ godot_java->alert(p_alert, p_title);
+}
+
void OS_Android::initialize_core() {
OS_Unix::initialize_core();
if (use_apk_expansion)
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
else {
-#ifdef USE_JAVA_FILE_ACCESS
- FileAccess::make_default<FileAccessBufferedFA<FileAccessJAndroid>>(FileAccess::ACCESS_RESOURCES);
-#else
- //FileAccess::make_default<FileAccessBufferedFA<FileAccessAndroid> >(FileAccess::ACCESS_RESOURCES);
FileAccess::make_default<FileAccessAndroid>(FileAccess::ACCESS_RESOURCES);
-#endif
}
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA);
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM);
- //FileAccessBufferedFA<FileAccessUnix>::make_default();
if (use_apk_expansion)
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES);
else
@@ -147,18 +163,19 @@ MainLoop *OS_Android::get_main_loop() const {
void OS_Android::main_loop_begin() {
if (main_loop)
- main_loop->init();
+ main_loop->initialize();
}
bool OS_Android::main_loop_iterate() {
if (!main_loop)
return false;
+ DisplayServerAndroid::get_singleton()->process_events();
return Main::iteration();
}
void OS_Android::main_loop_end() {
if (main_loop)
- main_loop->finish();
+ main_loop->finalize();
}
void OS_Android::main_loop_focusout() {
@@ -171,10 +188,6 @@ void OS_Android::main_loop_focusin() {
audio_driver_android.set_pause(false);
}
-void OS_Android::main_loop_request_go_back() {
- DisplayServerAndroid::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_GO_BACK_REQUEST);
-}
-
Error OS_Android::shell_open(String p_uri) {
return godot_io_java->open_uri(p_uri);
}
@@ -200,32 +213,31 @@ String OS_Android::get_model_name() const {
return OS_Unix::get_model_name();
}
+String OS_Android::get_data_path() const {
+ return get_user_data_dir();
+}
+
String OS_Android::get_user_data_dir() const {
if (data_dir_cache != String())
return data_dir_cache;
String data_dir = godot_io_java->get_user_data_dir();
if (data_dir != "") {
- //store current dir
- char real_current_dir_name[2048];
- getcwd(real_current_dir_name, 2048);
-
- //go to data dir
- chdir(data_dir.utf8().get_data());
-
- //get actual data dir, so we resolve potential symlink (Android 6.0+ seems to use symlink)
- char data_current_dir_name[2048];
- getcwd(data_current_dir_name, 2048);
-
- //cache by parsing utf8
- data_dir_cache.parse_utf8(data_current_dir_name);
-
- //restore original dir so we don't mess things up
- chdir(real_current_dir_name);
-
+ data_dir_cache = _remove_symlink(data_dir);
return data_dir_cache;
}
+ return ".";
+}
+
+String OS_Android::get_cache_path() const {
+ if (cache_dir_cache != String())
+ return cache_dir_cache;
+ String cache_dir = godot_io_java->get_cache_dir();
+ if (cache_dir != "") {
+ cache_dir_cache = _remove_symlink(cache_dir);
+ return cache_dir_cache;
+ }
return ".";
}
@@ -237,8 +249,8 @@ String OS_Android::get_unique_id() const {
return OS::get_unique_id();
}
-String OS_Android::get_system_dir(SystemDir p_dir) const {
- return godot_io_java->get_system_dir(p_dir);
+String OS_Android::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
+ return godot_io_java->get_system_dir(p_dir, p_shared_storage);
}
void OS_Android::set_display_size(const Size2i &p_size) {
@@ -249,16 +261,8 @@ Size2i OS_Android::get_display_size() const {
return display_size;
}
-void OS_Android::set_context_is_16_bits(bool p_is_16) {
-#if defined(OPENGL_ENABLED)
- //use_16bits_fbo = p_is_16;
- //if (rasterizer)
- // rasterizer->set_force_16_bits_fbo(p_is_16);
-#endif
-}
-
void OS_Android::set_opengl_extensions(const char *p_gl_extensions) {
-#if defined(OPENGL_ENABLED)
+#if defined(GLES3_ENABLED)
ERR_FAIL_COND(!p_gl_extensions);
gl_extensions = p_gl_extensions;
#endif
@@ -310,7 +314,7 @@ OS_Android::OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_god
main_loop = nullptr;
-#if defined(OPENGL_ENABLED)
+#if defined(GLES3_ENABLED)
gl_extensions = nullptr;
use_gl2 = false;
#endif
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index cac7efaa88..a62f79952c 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,7 +31,6 @@
#ifndef OS_ANDROID_H
#define OS_ANDROID_H
-#include "audio_driver_jandroid.h"
#include "audio_driver_opensl.h"
#include "core/os/main_loop.h"
#include "drivers/unix/os_unix.h"
@@ -48,8 +47,7 @@ private:
bool use_apk_expansion;
-#if defined(OPENGL_ENABLED)
- bool use_16bits_fbo;
+#if defined(GLES3_ENABLED)
const char *gl_extensions;
#endif
@@ -58,8 +56,8 @@ private:
#endif
mutable String data_dir_cache;
+ mutable String cache_dir_cache;
- //AudioDriverAndroid audio_driver_android;
AudioDriverOpenSL audio_driver_android;
MainLoop *main_loop;
@@ -68,15 +66,15 @@ private:
GodotIOJavaWrapper *godot_io_java;
public:
- virtual void initialize_core();
- virtual void initialize();
+ virtual void initialize_core() override;
+ virtual void initialize() override;
- virtual void initialize_joypads();
+ virtual void initialize_joypads() override;
- virtual void set_main_loop(MainLoop *p_main_loop);
- virtual void delete_main_loop();
+ virtual void set_main_loop(MainLoop *p_main_loop) override;
+ virtual void delete_main_loop() override;
- virtual void finalize();
+ virtual void finalize() override;
typedef int64_t ProcessID;
@@ -84,18 +82,19 @@ public:
GodotJavaWrapper *get_godot_java();
GodotIOJavaWrapper *get_godot_io_java();
- virtual bool request_permission(const String &p_name);
- virtual bool request_permissions();
- virtual Vector<String> get_granted_permissions() const;
+ virtual bool request_permission(const String &p_name) override;
+ virtual bool request_permissions() override;
+ virtual Vector<String> get_granted_permissions() const override;
- virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
+ virtual void alert(const String &p_alert, const String &p_title) override;
- virtual String get_name() const;
- virtual MainLoop *get_main_loop() const;
+ virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override;
+
+ virtual String get_name() const override;
+ virtual MainLoop *get_main_loop() const override;
void main_loop_begin();
bool main_loop_iterate();
- void main_loop_request_go_back();
void main_loop_end();
void main_loop_focusout();
void main_loop_focusin();
@@ -103,25 +102,26 @@ public:
void set_display_size(const Size2i &p_size);
Size2i get_display_size() const;
- void set_context_is_16_bits(bool p_is_16);
void set_opengl_extensions(const char *p_gl_extensions);
void set_native_window(ANativeWindow *p_native_window);
ANativeWindow *get_native_window() const;
- virtual Error shell_open(String p_uri);
- virtual String get_user_data_dir() const;
- virtual String get_resource_dir() const;
- virtual String get_locale() const;
- virtual String get_model_name() const;
+ virtual Error shell_open(String p_uri) override;
+ virtual String get_user_data_dir() const override;
+ virtual String get_data_path() const override;
+ virtual String get_cache_path() const override;
+ virtual String get_resource_dir() const override;
+ virtual String get_locale() const override;
+ virtual String get_model_name() const override;
- virtual String get_unique_id() const;
+ virtual String get_unique_id() const override;
- virtual String get_system_dir(SystemDir p_dir) const;
+ virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;
- void vibrate_handheld(int p_duration_ms);
+ void vibrate_handheld(int p_duration_ms) override;
- virtual bool _check_internal_feature_support(const String &p_feature);
+ virtual bool _check_internal_feature_support(const String &p_feature) override;
OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion);
~OS_Android();
};
diff --git a/platform/android/platform_config.h b/platform/android/platform_config.h
index c5e896c4e1..601db9951f 100644
--- a/platform/android/platform_config.h
+++ b/platform/android/platform_config.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/android/plugin/godot_plugin_config.h b/platform/android/plugin/godot_plugin_config.h
deleted file mode 100644
index ea3c7b4f55..0000000000
--- a/platform/android/plugin/godot_plugin_config.h
+++ /dev/null
@@ -1,268 +0,0 @@
-/*************************************************************************/
-/* godot_plugin_config.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef GODOT_PLUGIN_CONFIG_H
-#define GODOT_PLUGIN_CONFIG_H
-
-#include "core/error_list.h"
-#include "core/io/config_file.h"
-#include "core/ustring.h"
-
-static const char *PLUGIN_CONFIG_EXT = ".gdap";
-
-static const char *CONFIG_SECTION = "config";
-static const char *CONFIG_NAME_KEY = "name";
-static const char *CONFIG_BINARY_TYPE_KEY = "binary_type";
-static const char *CONFIG_BINARY_KEY = "binary";
-
-static const char *DEPENDENCIES_SECTION = "dependencies";
-static const char *DEPENDENCIES_LOCAL_KEY = "local";
-static const char *DEPENDENCIES_REMOTE_KEY = "remote";
-static const char *DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY = "custom_maven_repos";
-
-static const char *BINARY_TYPE_LOCAL = "local";
-static const char *BINARY_TYPE_REMOTE = "remote";
-
-static const char *PLUGIN_VALUE_SEPARATOR = "|";
-
-/*
- The `config` section and fields are required and defined as follow:
-- **name**: name of the plugin
-- **binary_type**: can be either `local` or `remote`. The type affects the **binary** field
-- **binary**:
- - if **binary_type** is `local`, then this should be the filename of the plugin `aar` file in the `res://android/plugins` directory (e.g: `MyPlugin.aar`).
- - if **binary_type** is `remote`, then this should be a declaration for a remote gradle binary (e.g: "org.godot.example:my-plugin:0.0.0").
-
-The `dependencies` section and fields are optional and defined as follow:
-- **local**: contains a list of local `.aar` binary files the plugin depends on. The local binary dependencies must also be located in the `res://android/plugins` directory.
-- **remote**: contains a list of remote binary gradle dependencies for the plugin.
-- **custom_maven_repos**: contains a list of urls specifying custom maven repos required for the plugin's dependencies.
-
- See https://github.com/godotengine/godot/issues/38157#issuecomment-618773871
- */
-struct PluginConfig {
- // Set to true when the config file is properly loaded.
- bool valid_config = false;
- // Unix timestamp of last change to this plugin.
- uint64_t last_updated = 0;
-
- // Required config section
- String name;
- String binary_type;
- String binary;
-
- // Optional dependencies section
- Vector<String> local_dependencies;
- Vector<String> remote_dependencies;
- Vector<String> custom_maven_repos;
-};
-
-/*
- * Set of prebuilt plugins.
- * Currently unused, this is just for future reference:
- */
-// static const PluginConfig MY_PREBUILT_PLUGIN = {
-// /*.valid_config =*/true,
-// /*.last_updated =*/0,
-// /*.name =*/"GodotPayment",
-// /*.binary_type =*/"local",
-// /*.binary =*/"res://android/build/libs/plugins/GodotPayment.release.aar",
-// /*.local_dependencies =*/{},
-// /*.remote_dependencies =*/String("com.android.billingclient:billing:2.2.1").split("|"),
-// /*.custom_maven_repos =*/{}
-// };
-
-static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) {
- String absolute_path;
- if (!dependency_path.empty()) {
- if (dependency_path.is_abs_path()) {
- absolute_path = ProjectSettings::get_singleton()->globalize_path(dependency_path);
- } else {
- absolute_path = plugin_config_dir.plus_file(dependency_path);
- }
- }
-
- return absolute_path;
-}
-
-static inline PluginConfig resolve_prebuilt_plugin(PluginConfig prebuilt_plugin, String plugin_config_dir) {
- PluginConfig resolved = prebuilt_plugin;
- resolved.binary = resolved.binary_type == BINARY_TYPE_LOCAL ? resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.binary) : prebuilt_plugin.binary;
- if (!prebuilt_plugin.local_dependencies.empty()) {
- resolved.local_dependencies.clear();
- for (int i = 0; i < prebuilt_plugin.local_dependencies.size(); i++) {
- resolved.local_dependencies.push_back(resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.local_dependencies[i]));
- }
- }
- return resolved;
-}
-
-static inline Vector<PluginConfig> get_prebuilt_plugins(String plugins_base_dir) {
- Vector<PluginConfig> prebuilt_plugins;
- // prebuilt_plugins.push_back(resolve_prebuilt_plugin(MY_PREBUILT_PLUGIN, plugins_base_dir));
- return prebuilt_plugins;
-}
-
-static inline bool is_plugin_config_valid(PluginConfig plugin_config) {
- bool valid_name = !plugin_config.name.empty();
- bool valid_binary_type = plugin_config.binary_type == BINARY_TYPE_LOCAL ||
- plugin_config.binary_type == BINARY_TYPE_REMOTE;
-
- bool valid_binary = false;
- if (valid_binary_type) {
- valid_binary = !plugin_config.binary.empty() &&
- (plugin_config.binary_type == BINARY_TYPE_REMOTE ||
- FileAccess::exists(plugin_config.binary));
- }
-
- bool valid_local_dependencies = true;
- if (!plugin_config.local_dependencies.empty()) {
- for (int i = 0; i < plugin_config.local_dependencies.size(); i++) {
- if (!FileAccess::exists(plugin_config.local_dependencies[i])) {
- valid_local_dependencies = false;
- break;
- }
- }
- }
- return valid_name && valid_binary && valid_binary_type && valid_local_dependencies;
-}
-
-static inline uint64_t get_plugin_modification_time(const PluginConfig &plugin_config, const String &config_path) {
- uint64_t last_updated = FileAccess::get_modified_time(config_path);
- last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary));
-
- for (int i = 0; i < plugin_config.local_dependencies.size(); i++) {
- String binary = plugin_config.local_dependencies.get(i);
- last_updated = MAX(last_updated, FileAccess::get_modified_time(binary));
- }
-
- return last_updated;
-}
-
-static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const String &path) {
- PluginConfig plugin_config = {};
-
- if (config_file.is_valid()) {
- Error err = config_file->load(path);
- if (err == OK) {
- String config_base_dir = path.get_base_dir();
-
- plugin_config.name = config_file->get_value(CONFIG_SECTION, CONFIG_NAME_KEY, String());
- plugin_config.binary_type = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_TYPE_KEY, String());
-
- String binary_path = config_file->get_value(CONFIG_SECTION, CONFIG_BINARY_KEY, String());
- plugin_config.binary = plugin_config.binary_type == BINARY_TYPE_LOCAL ? resolve_local_dependency_path(config_base_dir, binary_path) : binary_path;
-
- if (config_file->has_section(DEPENDENCIES_SECTION)) {
- Vector<String> local_dependencies_paths = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_LOCAL_KEY, Vector<String>());
- if (!local_dependencies_paths.empty()) {
- for (int i = 0; i < local_dependencies_paths.size(); i++) {
- plugin_config.local_dependencies.push_back(resolve_local_dependency_path(config_base_dir, local_dependencies_paths[i]));
- }
- }
-
- plugin_config.remote_dependencies = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_REMOTE_KEY, Vector<String>());
- plugin_config.custom_maven_repos = config_file->get_value(DEPENDENCIES_SECTION, DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY, Vector<String>());
- }
-
- plugin_config.valid_config = is_plugin_config_valid(plugin_config);
- plugin_config.last_updated = get_plugin_modification_time(plugin_config, path);
- }
- }
-
- return plugin_config;
-}
-
-static inline String get_plugins_binaries(String binary_type, Vector<PluginConfig> plugins_configs) {
- String plugins_binaries;
- if (!plugins_configs.empty()) {
- Vector<String> binaries;
- for (int i = 0; i < plugins_configs.size(); i++) {
- PluginConfig config = plugins_configs[i];
- if (!config.valid_config) {
- continue;
- }
-
- if (config.binary_type == binary_type) {
- binaries.push_back(config.binary);
- }
-
- if (binary_type == BINARY_TYPE_LOCAL) {
- binaries.append_array(config.local_dependencies);
- }
-
- if (binary_type == BINARY_TYPE_REMOTE) {
- binaries.append_array(config.remote_dependencies);
- }
- }
-
- plugins_binaries = String(PLUGIN_VALUE_SEPARATOR).join(binaries);
- }
-
- return plugins_binaries;
-}
-
-static inline String get_plugins_custom_maven_repos(Vector<PluginConfig> plugins_configs) {
- String custom_maven_repos;
- if (!plugins_configs.empty()) {
- Vector<String> repos_urls;
- for (int i = 0; i < plugins_configs.size(); i++) {
- PluginConfig config = plugins_configs[i];
- if (!config.valid_config) {
- continue;
- }
-
- repos_urls.append_array(config.custom_maven_repos);
- }
-
- custom_maven_repos = String(PLUGIN_VALUE_SEPARATOR).join(repos_urls);
- }
- return custom_maven_repos;
-}
-
-static inline String get_plugins_names(Vector<PluginConfig> plugins_configs) {
- String plugins_names;
- if (!plugins_configs.empty()) {
- Vector<String> names;
- for (int i = 0; i < plugins_configs.size(); i++) {
- PluginConfig config = plugins_configs[i];
- if (!config.valid_config) {
- continue;
- }
-
- names.push_back(config.name);
- }
- plugins_names = String(PLUGIN_VALUE_SEPARATOR).join(names);
- }
-
- return plugins_names;
-}
-
-#endif // GODOT_PLUGIN_CONFIG_H
diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp
index d2528bebeb..6a20d5eafc 100644
--- a/platform/android/plugin/godot_plugin_jni.cpp
+++ b/platform/android/plugin/godot_plugin_jni.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,9 +30,9 @@
#include "godot_plugin_jni.h"
-#include <core/engine.h>
-#include <core/error_macros.h>
-#include <core/project_settings.h>
+#include <core/config/engine.h>
+#include <core/config/project_settings.h>
+#include <core/error/error_macros.h>
#include <platform/android/api/jni_singleton.h>
#include <platform/android/jni_utils.h>
#include <platform/android/string_android.h>
@@ -41,9 +41,9 @@ static HashMap<String, JNISingleton *> jni_singletons;
extern "C" {
-JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jobject obj, jstring name) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jclass clazz, jstring name, jobject obj) {
String singname = jstring_to_string(name, env);
- JNISingleton *s = (JNISingleton *)ClassDB::instance("JNISingleton");
+ JNISingleton *s = (JNISingleton *)ClassDB::instantiate("JNISingleton");
s->set_instance(env->NewGlobalRef(obj));
jni_singletons[singname] = s;
@@ -51,7 +51,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis
ProjectSettings::get_singleton()->set(singname, s);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jclass clazz, jstring sname, jstring name, jstring ret, jobjectArray args) {
String singname = jstring_to_string(sname, env);
ERR_FAIL_COND(!jni_singletons.has(singname));
@@ -83,7 +83,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis
s->add_method(mname, mid, types, get_jni_type(retval));
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jobject obj, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types) {
String singleton_name = jstring_to_string(j_plugin_name, env);
ERR_FAIL_COND(!jni_singletons.has(singleton_name));
@@ -104,7 +104,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis
singleton->add_signal(signal_name, types);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jobject obj, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params) {
String singleton_name = jstring_to_string(j_plugin_name, env);
ERR_FAIL_COND(!jni_singletons.has(singleton_name));
@@ -126,10 +126,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitS
env->DeleteLocalRef(j_param);
};
- singleton->emit_signal(signal_name, args, count);
+ singleton->emit_signal(SNAME(signal_name), args, count);
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jobject obj, jobjectArray gdnlib_paths) {
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jclass clazz, jobjectArray gdnlib_paths) {
int gdnlib_count = env->GetArrayLength(gdnlib_paths);
if (gdnlib_count == 0) {
return;
diff --git a/platform/android/plugin/godot_plugin_jni.h b/platform/android/plugin/godot_plugin_jni.h
index 80ce332e7c..b87f922e03 100644
--- a/platform/android/plugin/godot_plugin_jni.h
+++ b/platform/android/plugin/godot_plugin_jni.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -35,11 +35,11 @@
#include <jni.h>
extern "C" {
-JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jobject obj, jstring name);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jobject obj, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jobject obj, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jobject obj, jobjectArray gdnlib_paths);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jclass clazz, jstring name, jobject obj);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jclass clazz, jstring sname, jstring name, jstring ret, jobjectArray args);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jclass clazz, jobjectArray gdnlib_paths);
}
#endif // GODOT_PLUGIN_JNI_H
diff --git a/platform/android/string_android.h b/platform/android/string_android.h
index 88ccd3b652..3721315d3f 100644
--- a/platform/android/string_android.h
+++ b/platform/android/string_android.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,21 +30,21 @@
#ifndef STRING_ANDROID_H
#define STRING_ANDROID_H
-#include "core/ustring.h"
+#include "core/string/ustring.h"
#include "thread_jandroid.h"
#include <jni.h>
/**
* Converts JNI jstring to Godot String.
* @param source Source JNI string. If null an empty string is returned.
- * @param env JNI environment instance. If null obtained by ThreadAndroid::get_env().
+ * @param env JNI environment instance. If null obtained by get_jni_env().
* @return Godot string instance.
*/
static inline String jstring_to_string(jstring source, JNIEnv *env = nullptr) {
String result;
if (source) {
if (!env) {
- env = ThreadAndroid::get_env();
+ env = get_jni_env();
}
const char *const source_utf8 = env->GetStringUTFChars(source, nullptr);
if (source_utf8) {
diff --git a/platform/android/thread_jandroid.cpp b/platform/android/thread_jandroid.cpp
index 13aa313ebf..ba379c8d43 100644
--- a/platform/android/thread_jandroid.cpp
+++ b/platform/android/thread_jandroid.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,116 +30,54 @@
#include "thread_jandroid.h"
-#include "core/os/memory.h"
-#include "core/safe_refcount.h"
-#include "core/script_language.h"
+#include <android/log.h>
-static void _thread_id_key_destr_callback(void *p_value) {
- memdelete(static_cast<Thread::ID *>(p_value));
-}
-
-static pthread_key_t _create_thread_id_key() {
- pthread_key_t key;
- pthread_key_create(&key, &_thread_id_key_destr_callback);
- return key;
-}
-
-pthread_key_t ThreadAndroid::thread_id_key = _create_thread_id_key();
-Thread::ID ThreadAndroid::next_thread_id = 0;
-
-Thread::ID ThreadAndroid::get_id() const {
- return id;
-}
+#include "core/os/thread.h"
-Thread *ThreadAndroid::create_thread_jandroid() {
- return memnew(ThreadAndroid);
-}
-
-void *ThreadAndroid::thread_callback(void *userdata) {
- ThreadAndroid *t = reinterpret_cast<ThreadAndroid *>(userdata);
- setup_thread();
- ScriptServer::thread_enter(); //scripts may need to attach a stack
- t->id = atomic_increment(&next_thread_id);
- pthread_setspecific(thread_id_key, (void *)memnew(ID(t->id)));
- t->callback(t->user);
- ScriptServer::thread_exit();
- return nullptr;
-}
+static JavaVM *java_vm = nullptr;
+static thread_local JNIEnv *env = nullptr;
-Thread *ThreadAndroid::create_func_jandroid(ThreadCreateCallback p_callback, void *p_user, const Settings &) {
- ThreadAndroid *tr = memnew(ThreadAndroid);
- tr->callback = p_callback;
- tr->user = p_user;
- pthread_attr_init(&tr->pthread_attr);
- pthread_attr_setdetachstate(&tr->pthread_attr, PTHREAD_CREATE_JOINABLE);
+// The logic here need to improve, init_thread/term_tread are designed to work with Thread::callback
+// Calling init_thread from setup_android_thread and get_jni_env to setup an env we're keeping and not detaching
+// could cause issues on app termination.
+//
+// We should be making sure that any thread started calls a nice cleanup function when it's done,
+// especially now that we use many more threads.
- pthread_create(&tr->pthread, &tr->pthread_attr, thread_callback, tr);
+static void init_thread() {
+ if (env) {
+ // thread never detached! just keep using...
+ return;
+ }
- return tr;
+ java_vm->AttachCurrentThread(&env, nullptr);
}
-Thread::ID ThreadAndroid::get_thread_id_func_jandroid() {
- void *value = pthread_getspecific(thread_id_key);
-
- if (value)
- return *static_cast<ID *>(value);
+static void term_thread() {
+ java_vm->DetachCurrentThread();
- ID new_id = atomic_increment(&next_thread_id);
- pthread_setspecific(thread_id_key, (void *)memnew(ID(new_id)));
- return new_id;
+ // this is no longer valid, must called init_thread to re-establish
+ env = nullptr;
}
-void ThreadAndroid::wait_to_finish_func_jandroid(Thread *p_thread) {
- ThreadAndroid *tp = static_cast<ThreadAndroid *>(p_thread);
- ERR_FAIL_COND(!tp);
- ERR_FAIL_COND(tp->pthread == 0);
-
- pthread_join(tp->pthread, nullptr);
- tp->pthread = 0;
+void init_thread_jandroid(JavaVM *p_jvm, JNIEnv *p_env) {
+ java_vm = p_jvm;
+ env = p_env;
+ Thread::_set_platform_funcs(nullptr, nullptr, &init_thread, &term_thread);
}
-void ThreadAndroid::_thread_destroyed(void *value) {
- /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
- JNIEnv *env = (JNIEnv *)value;
- if (env != nullptr) {
- java_vm->DetachCurrentThread();
- pthread_setspecific(jvm_key, nullptr);
+void setup_android_thread() {
+ if (!env) {
+ // !BAS! see remarks above
+ init_thread();
}
}
-pthread_key_t ThreadAndroid::jvm_key;
-JavaVM *ThreadAndroid::java_vm = nullptr;
-
-void ThreadAndroid::setup_thread() {
- if (pthread_getspecific(jvm_key))
- return; //already setup
- JNIEnv *env;
- java_vm->AttachCurrentThread(&env, nullptr);
- pthread_setspecific(jvm_key, (void *)env);
-}
-
-void ThreadAndroid::make_default(JavaVM *p_java_vm) {
- java_vm = p_java_vm;
- create_func = create_func_jandroid;
- get_thread_id_func = get_thread_id_func_jandroid;
- wait_to_finish_func = wait_to_finish_func_jandroid;
- pthread_key_create(&jvm_key, _thread_destroyed);
- setup_thread();
-}
-
-JNIEnv *ThreadAndroid::get_env() {
- if (!pthread_getspecific(jvm_key)) {
- setup_thread();
+JNIEnv *get_jni_env() {
+ if (!env) {
+ // !BAS! see remarks above
+ init_thread();
}
- JNIEnv *env = nullptr;
- java_vm->AttachCurrentThread(&env, nullptr);
return env;
}
-
-ThreadAndroid::ThreadAndroid() {
- pthread = 0;
-}
-
-ThreadAndroid::~ThreadAndroid() {
-}
diff --git a/platform/android/thread_jandroid.h b/platform/android/thread_jandroid.h
index 9cfcc64813..ff13ae911f 100644
--- a/platform/android/thread_jandroid.h
+++ b/platform/android/thread_jandroid.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,46 +28,14 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef THREAD_POSIX_H
-#define THREAD_POSIX_H
+#ifndef THREAD_JANDROID_H
+#define THREAD_JANDROID_H
-#include "core/os/thread.h"
#include <jni.h>
-#include <pthread.h>
-#include <sys/types.h>
-class ThreadAndroid : public Thread {
- static pthread_key_t thread_id_key;
- static ID next_thread_id;
+void init_thread_jandroid(JavaVM *p_jvm, JNIEnv *p_env);
- pthread_t pthread;
- pthread_attr_t pthread_attr;
- ThreadCreateCallback callback;
- void *user;
- ID id;
-
- static Thread *create_thread_jandroid();
-
- static void *thread_callback(void *userdata);
-
- static Thread *create_func_jandroid(ThreadCreateCallback p_callback, void *, const Settings &);
- static ID get_thread_id_func_jandroid();
- static void wait_to_finish_func_jandroid(Thread *p_thread);
-
- static void _thread_destroyed(void *value);
- ThreadAndroid();
-
- static pthread_key_t jvm_key;
- static JavaVM *java_vm;
-
-public:
- virtual ID get_id() const;
-
- static void make_default(JavaVM *p_java_vm);
- static void setup_thread();
- static JNIEnv *get_env();
-
- ~ThreadAndroid();
-};
+void setup_android_thread();
+JNIEnv *get_jni_env();
#endif
diff --git a/platform/android/vulkan/vulkan_context_android.cpp b/platform/android/vulkan/vulkan_context_android.cpp
index 5fb7a83da4..e24d1a4527 100644
--- a/platform/android/vulkan/vulkan_context_android.cpp
+++ b/platform/android/vulkan/vulkan_context_android.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -29,13 +29,18 @@
/*************************************************************************/
#include "vulkan_context_android.h"
-#include <vulkan/vulkan_android.h>
+
+#ifdef USE_VOLK
+#include <volk.h>
+#else
+#include <vulkan/vulkan.h>
+#endif
const char *VulkanContextAndroid::_get_platform_surface_extension() const {
return VK_KHR_ANDROID_SURFACE_EXTENSION_NAME;
}
-int VulkanContextAndroid::window_create(ANativeWindow *p_window, int p_width, int p_height) {
+int VulkanContextAndroid::window_create(ANativeWindow *p_window, DisplayServer::VSyncMode p_vsync_mode, int p_width, int p_height) {
VkAndroidSurfaceCreateInfoKHR createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
createInfo.pNext = nullptr;
@@ -43,18 +48,18 @@ int VulkanContextAndroid::window_create(ANativeWindow *p_window, int p_width, in
createInfo.window = p_window;
VkSurfaceKHR surface;
- VkResult err = vkCreateAndroidSurfaceKHR(_get_instance(), &createInfo, nullptr, &surface);
+ VkResult err = vkCreateAndroidSurfaceKHR(get_instance(), &createInfo, nullptr, &surface);
if (err != VK_SUCCESS) {
ERR_FAIL_V_MSG(-1, "vkCreateAndroidSurfaceKHR failed with error " + itos(err));
}
- return _window_create(DisplayServer::MAIN_WINDOW_ID, surface, p_width, p_height);
+ return _window_create(DisplayServer::MAIN_WINDOW_ID, p_vsync_mode, surface, p_width, p_height);
}
-VulkanContextAndroid::VulkanContextAndroid() {
- // TODO: fix validation layers
- use_validation_layers = false;
-}
+bool VulkanContextAndroid::_use_validation_layers() {
+ uint32_t count = 0;
+ _get_preferred_validation_layers(&count, nullptr);
-VulkanContextAndroid::~VulkanContextAndroid() {
+ // On Android, we use validation layers automatically if they were explicitly linked with the app.
+ return count > 0;
}
diff --git a/platform/android/vulkan/vulkan_context_android.h b/platform/android/vulkan/vulkan_context_android.h
index 6bd3cbee36..182ce33c97 100644
--- a/platform/android/vulkan/vulkan_context_android.h
+++ b/platform/android/vulkan/vulkan_context_android.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -36,13 +36,16 @@
struct ANativeWindow;
class VulkanContextAndroid : public VulkanContext {
- virtual const char *_get_platform_surface_extension() const;
+ virtual const char *_get_platform_surface_extension() const override;
public:
- int window_create(ANativeWindow *p_window, int p_width, int p_height);
+ int window_create(ANativeWindow *p_window, DisplayServer::VSyncMode p_vsync_mode, int p_width, int p_height);
- VulkanContextAndroid();
- ~VulkanContextAndroid();
+ VulkanContextAndroid() = default;
+ ~VulkanContextAndroid() override = default;
+
+protected:
+ bool _use_validation_layers() override;
};
#endif // VULKAN_CONTEXT_ANDROID_H