diff options
Diffstat (limited to 'platform/android')
42 files changed, 2789 insertions, 1614 deletions
diff --git a/platform/android/SCsub b/platform/android/SCsub index fd2a774c71..46f0703a65 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -18,6 +18,10 @@ android_files = [ 'java_class_wrapper.cpp', 'java_godot_wrapper.cpp', 'java_godot_io_wrapper.cpp', + 'jni_utils.cpp', + 'android_keys_utils.cpp', + 'vulkan/vk_renderer_jni.cpp', + 'plugin/godot_plugin_jni.cpp' ] env_android = env.Clone() diff --git a/platform/android/android_keys_utils.cpp b/platform/android/android_keys_utils.cpp new file mode 100644 index 0000000000..88874ba2c7 --- /dev/null +++ b/platform/android/android_keys_utils.cpp @@ -0,0 +1,43 @@ +/*************************************************************************/ +/* android_keys_utils.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 "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++) { + + if (_ak_to_keycode[i].keycode == p_code) { + + return _ak_to_keycode[i].keysym; + } + } + + return KEY_UNKNOWN; +} diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h new file mode 100644 index 0000000000..f076688ac8 --- /dev/null +++ b/platform/android/android_keys_utils.h @@ -0,0 +1,280 @@ +/*************************************************************************/ +/* android_keys_utils.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 ANDROID_KEYS_UTILS_H +#define ANDROID_KEYS_UTILS_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, + + // 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; +}; + +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_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, +*/ + +unsigned int android_get_keysym(unsigned int p_code); + +#endif // ANDROID_KEYS_UTILS_H diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp index 307bc3a169..222120f81f 100644 --- a/platform/android/audio_driver_opensl.cpp +++ b/platform/android/audio_driver_opensl.cpp @@ -328,13 +328,13 @@ AudioDriver::SpeakerMode AudioDriverOpenSL::get_speaker_mode() const { void AudioDriverOpenSL::lock() { - if (active && mutex) + if (active) mutex.lock(); } void AudioDriverOpenSL::unlock() { - if (active && mutex) + if (active) mutex.unlock(); } diff --git a/platform/android/detect.py b/platform/android/detect.py index 8b62360888..8f74ae0ef0 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -205,7 +205,6 @@ def configure(env): env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/include"]) env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++abi/include"]) - env.Append(CXXFLAGS=["-std=gnu++14"]) # Disable exceptions and rtti on non-tools (template) builds if env['tools']: diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index f52b511522..ebcac884db 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -144,7 +144,7 @@ Error DirAccessJAndroid::change_dir(String p_dir) { return OK; } -String DirAccessJAndroid::get_current_dir() { +String DirAccessJAndroid::get_current_dir(bool p_include_drive) { return "res://" + current_dir; } diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h index caeb4b58b9..8dab3e50ce 100644 --- a/platform/android/dir_access_jandroid.h +++ b/platform/android/dir_access_jandroid.h @@ -65,7 +65,7 @@ public: virtual String get_drive(int p_drive); virtual Error change_dir(String p_dir); ///< can be relative or absolute, return false on success - virtual String get_current_dir(); ///< return current dir location + virtual String get_current_dir(bool p_include_drive = true); ///< return current dir location virtual bool file_exists(String p_file); virtual bool dir_exists(String p_dir); diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index c618177475..e7d4bc6c51 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -688,6 +688,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { int xr_mode_index = p_preset->get("xr_features/xr_mode"); + String plugins = p_preset->get("custom_template/plugins"); + Vector<String> perms; const char **aperms = android_perms; @@ -851,6 +853,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } } + if (tname == "meta-data" && attrname == "value" && value == "custom_template_plugins_value") { + // Update the meta-data 'android:value' attribute with the list of enabled plugins. + string_table.write[attr_value] = plugins; + } + iofs += 20; } @@ -1363,6 +1370,7 @@ public: 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::STRING, "custom_template/plugins", PROPERTY_HINT_PLACEHOLDER_TEXT, "Plugin1,Plugin2,..."), "")); 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")); @@ -1743,260 +1751,6 @@ public: return list; } - void _update_custom_build_project() { - - DirAccessRef da = DirAccess::open("res://android"); - - ERR_FAIL_COND_MSG(!da, "Cannot open directory 'res://android'."); - Map<String, List<String> > directory_paths; - Map<String, List<String> > manifest_sections; - Map<String, List<String> > gradle_sections; - da->list_dir_begin(); - String d = da->get_next(); - while (d != String()) { - - if (!d.begins_with(".") && d != "build" && da->current_is_dir()) { //a dir and not the build dir - //add directories found - DirAccessRef ds = DirAccess::open(String("res://android").plus_file(d)); - if (ds) { - ds->list_dir_begin(); - String sd = ds->get_next(); - while (sd != String()) { - - if (!sd.begins_with(".") && ds->current_is_dir()) { - String key = sd.to_upper(); - if (!directory_paths.has(key)) { - directory_paths[key] = List<String>(); - } - String path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android").plus_file(d).plus_file(sd); - directory_paths[key].push_back(path); - print_line("Add: " + sd + ":" + path); - } - - sd = ds->get_next(); - } - ds->list_dir_end(); - } - //parse manifest - { - FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("AndroidManifest.conf"), FileAccess::READ); - if (f) { - - String section; - while (!f->eof_reached()) { - String l = f->get_line(); - String k = l.strip_edges(); - if (k.begins_with("[")) { - section = k.substr(1, k.length() - 2).strip_edges().to_upper(); - print_line("Section: " + section); - } else if (k != String()) { - if (!manifest_sections.has(section)) { - manifest_sections[section] = List<String>(); - } - manifest_sections[section].push_back(l); - } - } - - f->close(); - } - } - //parse gradle - { - FileAccessRef f = FileAccess::open(String("res://android").plus_file(d).plus_file("gradle.conf"), FileAccess::READ); - if (f) { - - String section; - while (!f->eof_reached()) { - String l = f->get_line().strip_edges(); - String k = l.strip_edges(); - if (k.begins_with("[")) { - section = k.substr(1, k.length() - 2).strip_edges().to_upper(); - print_line("Section: " + section); - } else if (k != String()) { - if (!gradle_sections.has(section)) { - gradle_sections[section] = List<String>(); - } - gradle_sections[section].push_back(l); - } - } - } - } - } - d = da->get_next(); - } - da->list_dir_end(); - - { //fix gradle build - - String new_file; - { - FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::READ); - if (f) { - - while (!f->eof_reached()) { - String l = f->get_line(); - - if (l.begins_with("//CHUNK_")) { - String text = l.replace_first("//CHUNK_", ""); - int begin_pos = text.find("_BEGIN"); - if (begin_pos != -1) { - text = text.substr(0, begin_pos); - text = text.to_upper(); //just in case - - String end_marker = "//CHUNK_" + text + "_END"; - size_t pos = f->get_position(); - bool found = false; - while (!f->eof_reached()) { - l = f->get_line(); - if (l.begins_with(end_marker)) { - found = true; - break; - } - } - - new_file += "//CHUNK_" + text + "_BEGIN\n"; - - if (!found) { - ERR_PRINT("No end marker found in build.gradle for chunk: " + text); - f->seek(pos); - } else { - - //add chunk lines - if (gradle_sections.has(text)) { - for (List<String>::Element *E = gradle_sections[text].front(); E; E = E->next()) { - new_file += E->get() + "\n"; - } - } - new_file += end_marker + "\n"; - } - } else { - new_file += l + "\n"; //pass line by - } - } else if (l.begins_with("//DIR_")) { - String text = l.replace_first("//DIR_", ""); - int begin_pos = text.find("_BEGIN"); - if (begin_pos != -1) { - text = text.substr(0, begin_pos); - text = text.to_upper(); //just in case - - String end_marker = "//DIR_" + text + "_END"; - size_t pos = f->get_position(); - bool found = false; - while (!f->eof_reached()) { - l = f->get_line(); - if (l.begins_with(end_marker)) { - found = true; - break; - } - } - - new_file += "//DIR_" + text + "_BEGIN\n"; - - if (!found) { - ERR_PRINT("No end marker found in build.gradle for dir: " + text); - f->seek(pos); - } else { - //add chunk lines - if (directory_paths.has(text)) { - for (List<String>::Element *E = directory_paths[text].front(); E; E = E->next()) { - new_file += ",'" + E->get().replace("'", "\'") + "'"; - new_file += "\n"; - } - } - new_file += end_marker + "\n"; - } - } else { - new_file += l + "\n"; //pass line by - } - - } else { - new_file += l + "\n"; - } - } - } - } - - FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::WRITE); - f->store_string(new_file); - f->close(); - } - - { //fix manifest - - String new_file; - { - FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::READ); - if (f) { - - while (!f->eof_reached()) { - String l = f->get_line(); - - if (l.begins_with("<!--CHUNK_")) { - String text = l.replace_first("<!--CHUNK_", ""); - int begin_pos = text.find("_BEGIN-->"); - if (begin_pos != -1) { - text = text.substr(0, begin_pos); - text = text.to_upper(); //just in case - - String end_marker = "<!--CHUNK_" + text + "_END-->"; - size_t pos = f->get_position(); - bool found = false; - while (!f->eof_reached()) { - l = f->get_line(); - if (l.begins_with(end_marker)) { - found = true; - break; - } - } - - new_file += "<!--CHUNK_" + text + "_BEGIN-->\n"; - - if (!found) { - ERR_PRINT("No end marker found in AndroidManifest.xml for chunk: " + text); - f->seek(pos); - } else { - //add chunk lines - if (manifest_sections.has(text)) { - for (List<String>::Element *E = manifest_sections[text].front(); E; E = E->next()) { - new_file += E->get() + "\n"; - } - } - new_file += end_marker + "\n"; - } - } else { - new_file += l + "\n"; //pass line by - } - - } else if (l.strip_edges().begins_with("<application")) { - String last_tag = "android:icon=\"@mipmap/icon\""; - int last_tag_pos = l.find(last_tag); - if (last_tag_pos == -1) { - ERR_PRINT("Not adding application attributes as the expected tag was not found in '<application': " + last_tag); - new_file += l + "\n"; - } else { - String base = l.substr(0, last_tag_pos + last_tag.length()); - if (manifest_sections.has("application_attribs")) { - for (List<String>::Element *E = manifest_sections["application_attribs"].front(); E; E = E->next()) { - String to_add = E->get().strip_edges(); - base += " " + to_add + " "; - } - } - base += ">\n"; - new_file += base; - } - } else { - new_file += l + "\n"; - } - } - } - } - - FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::WRITE); - f->store_string(new_file); - f->close(); - } - } - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); @@ -2025,8 +1779,6 @@ public: 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'."); - _update_custom_build_project(); - OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required String build_command; @@ -2037,14 +1789,18 @@ public: #endif String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build"); + String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins"); build_command = build_path.plus_file(build_command); String package_name = get_package_name(p_preset->get("package/unique_name")); + String plugins = p_preset->get("custom_template/plugins"); List<String> cmdline; cmdline.push_back("build"); cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name. + cmdline.push_back("-Pcustom_template_plugins_dir=" + plugins_dir); // argument to specify the plugins directory. + cmdline.push_back("-Pcustom_template_plugins=" + plugins); // argument to specify the list of plugins to enable. cmdline.push_back("-p"); // argument to specify the start directory. cmdline.push_back(build_path); // start directory. /*{ used for debug diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 4d2eb1ef65..cc480d1c84 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -6,9 +6,6 @@ android:versionName="1.0" android:installLocation="auto" > - <!-- Adding custom text to the manifest is fine, but do it outside the custom USER and APPLICATION BEGIN/END comments, --> - <!-- as that gets rewritten. --> - <supports-screens android:smallScreens="true" android:normalScreens="true" @@ -19,14 +16,11 @@ android:glEsVersion="0x00020000" android:required="true" /> -<!-- Custom user permissions XML added by add-ons. It's recommended to add them from the export preset, though. --> -<!--CHUNK_USER_PERMISSIONS_BEGIN--> -<!--CHUNK_USER_PERMISSIONS_END--> - - <!-- Any tag in this line after android:icon will be erased when doing custom builds. --> - <!-- If you want to add tags manually, do before it. --> - <!-- WARNING: This should stay on a single line until the parsing code is improved. See GH-32414. --> - <application android:label="@string/godot_project_name_string" android:allowBackup="false" tools:ignore="GoogleAppIndexingWarning" android:icon="@mipmap/icon" > + <application + android:label="@string/godot_project_name_string" + android:allowBackup="false" + tools:ignore="GoogleAppIndexingWarning" + android:icon="@mipmap/icon" > <!-- 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. --> @@ -36,6 +30,11 @@ android:name="xr_mode_metadata_name" android:value="xr_mode_metadata_value" /> + <!-- Metadata populated at export time and used by Godot to figure out which plugins must be enabled. --> + <meta-data + android:name="custom_template_plugins" + android:value="custom_template_plugins_value"/> + <activity android:name=".GodotApp" android:label="@string/godot_project_name_string" @@ -52,10 +51,6 @@ </intent-filter> </activity> -<!-- Custom application XML added by add-ons. --> -<!--CHUNK_APPLICATION_BEGIN--> -<!--CHUNK_APPLICATION_END--> - </application> </manifest> diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 2e4f2ffab0..5e37f538e9 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -1,7 +1,4 @@ // Gradle build config for Godot Engine's Android port. -// -// Do not remove/modify comments ending with BEGIN/END, they are used to inject -// addon-specific configuration. apply from: 'config.gradle' buildscript { @@ -10,13 +7,10 @@ buildscript { repositories { google() jcenter() -//CHUNK_BUILDSCRIPT_REPOSITORIES_BEGIN -//CHUNK_BUILDSCRIPT_REPOSITORIES_END } dependencies { classpath libraries.androidGradlePlugin -//CHUNK_BUILDSCRIPT_DEPENDENCIES_BEGIN -//CHUNK_BUILDSCRIPT_DEPENDENCIES_END + classpath libraries.kotlinGradlePlugin } } @@ -27,24 +21,35 @@ allprojects { mavenCentral() google() jcenter() -//CHUNK_ALLPROJECTS_REPOSITORIES_BEGIN -//CHUNK_ALLPROJECTS_REPOSITORIES_END } } dependencies { implementation libraries.supportCoreUtils + implementation libraries.kotlinStdLib + implementation libraries.v4Support if (rootProject.findProject(":lib")) { implementation project(":lib") + } else if (rootProject.findProject(":godot:lib")) { + implementation project(":godot:lib") } else { // Custom build mode. In this scenario this project is the only one around and the Godot // library is available through the pre-generated godot-lib.*.aar android archive files. debugImplementation fileTree(dir: 'libs/debug', include: ['*.jar', '*.aar']) releaseImplementation fileTree(dir: 'libs/release', include: ['*.jar', '*.aar']) } -//CHUNK_DEPENDENCIES_BEGIN -//CHUNK_DEPENDENCIES_END + + // Godot prebuilt plugins + implementation fileTree(dir: 'libs/plugins', include: ["GodotPayment*.aar"]) + + // Godot user plugins dependencies + String pluginsDir = getGodotPluginsDirectory() + String[] pluginsBinaries = getGodotPluginsBinaries() + if (pluginsDir != null && !pluginsDir.isEmpty() && + pluginsBinaries != null && pluginsBinaries.size() > 0) { + implementation fileTree(dir: pluginsDir, include: pluginsBinaries) + } } android { @@ -56,8 +61,6 @@ android { applicationId getExportPackageName() minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk -//CHUNK_ANDROID_DEFAULTCONFIG_BEGIN -//CHUNK_ANDROID_DEFAULTCONFIG_END } lintOptions { @@ -79,37 +82,13 @@ android { sourceSets { main { manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = [ - 'src' -//DIR_SRC_BEGIN -//DIR_SRC_END - ] - res.srcDirs = [ - 'res' -//DIR_RES_BEGIN -//DIR_RES_END - ] - aidl.srcDirs = [ - 'aidl' -//DIR_AIDL_BEGIN -//DIR_AIDL_END - ] - assets.srcDirs = [ - 'assets' -//DIR_ASSETS_BEGIN -//DIR_ASSETS_END - ] + java.srcDirs = ['src'] + res.srcDirs = ['res'] + aidl.srcDirs = ['aidl'] + assets.srcDirs = ['assets'] } - debug.jniLibs.srcDirs = [ - 'libs/debug' -//DIR_JNI_DEBUG_BEGIN -//DIR_JNI_DEBUG_END - ] - release.jniLibs.srcDirs = [ - 'libs/release' -//DIR_JNI_RELEASE_BEGIN -//DIR_JNI_RELEASE_END - ] + debug.jniLibs.srcDirs = ['libs/debug'] + release.jniLibs.srcDirs = ['libs/release'] } applicationVariants.all { variant -> @@ -118,6 +97,3 @@ android { } } } - -//CHUNK_GLOBAL_BEGIN -//CHUNK_GLOBAL_END diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 5550d3099d..eaaefcbccb 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,24 +1,66 @@ ext.versions = [ - androidGradlePlugin : '3.4.2', - compileSdk : 28, - minSdk : 18, - targetSdk : 28, - buildTools : '28.0.3', - supportCoreUtils : '28.0.0' + androidGradlePlugin: '3.6.0', + compileSdk : 29, + minSdk : 18, + targetSdk : 29, + buildTools : '29.0.1', + supportCoreUtils : '28.0.0', + kotlinVersion : '1.3.61', + v4Support : '28.0.0' ] ext.libraries = [ - androidGradlePlugin : "com.android.tools.build:gradle:$versions.androidGradlePlugin", - supportCoreUtils : "com.android.support:support-core-utils:$versions.supportCoreUtils" + androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin", + supportCoreUtils : "com.android.support:support-core-utils:$versions.supportCoreUtils", + kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion", + kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlinVersion", + v4Support : "com.android.support:support-v4:$versions.v4Support" ] ext.getExportPackageName = { -> - // Retrieve the app id from the project property set by the Godot build command. - String appId = project.hasProperty("export_package_name") ? project.property("export_package_name") : "" - // Check if the app id is valid, otherwise use the default. - if (appId == null || appId.isEmpty()) { - appId = "com.godot.game" - } - return appId + // Retrieve the app id from the project property set by the Godot build command. + String appId = project.hasProperty("export_package_name") ? project.property("export_package_name") : "" + // Check if the app id is valid, otherwise use the default. + if (appId == null || appId.isEmpty()) { + appId = "com.godot.game" + } + return appId +} + +/** + * Parse the project properties for the 'custom_template_plugins' property and return + * their binaries for inclusion in the build dependencies. + * + * The listed plugins must have their binaries in the project plugins directory. + */ +ext.getGodotPluginsBinaries = { -> + String[] binDeps = [] + + // Retrieve the list of enabled plugins. + if (project.hasProperty("custom_template_plugins")) { + String pluginsList = project.property("custom_template_plugins") + if (pluginsList != null && !pluginsList.trim().isEmpty()) { + for (String plugin : pluginsList.split(",")) { + binDeps += plugin.trim() + "*.aar" + } + } + } + + return binDeps +} + +/** + * Parse the project properties for the 'custom_template_plugins_dir' property and return + * its value. + * + * The returned value is the directory containing user plugins. + */ +ext.getGodotPluginsDirectory = { -> + // The plugins directory is provided by the 'custom_template_plugins_dir' property. + String pluginsDir = project.hasProperty("custom_template_plugins_dir") + ? project.property("custom_template_plugins_dir") + : "" + + return pluginsDir } diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index 2052017888..976a5bda99 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -9,6 +9,7 @@ buildscript { } dependencies { classpath libraries.androidGradlePlugin + classpath libraries.kotlinGradlePlugin } } @@ -24,7 +25,7 @@ ext { sconsExt = org.gradle.internal.os.OperatingSystem.current().isWindows() ? ".bat" : "" supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"] - supportedTargets = ['release':"release", 'debug':"release_debug"] + supportedTargets = ['release': "release", 'debug': "release_debug"] // Used by gradle to specify which architecture to build for by default when running `./gradlew build`. // This command is usually used by Android Studio. @@ -64,10 +65,10 @@ task copyReleaseBinaryToBin(type: Copy) { } /** - * Copy the Godot android library archive debug file into the app debug libs directory. + * Copy the Godot android library archive debug file into the app module debug libs directory. * Depends on the library build task to ensure the AAR file is generated prior to copying. */ -task copyDebugAAR(type: Copy) { +task copyDebugAARToAppModule(type: Copy) { dependsOn ':lib:assembleDebug' from('lib/build/outputs/aar') into('app/libs/debug') @@ -75,16 +76,45 @@ task copyDebugAAR(type: Copy) { } /** - * Copy the Godot android library archive release file into the app release libs directory. + * Copy the Godot android library archive debug file into the root bin directory. * Depends on the library build task to ensure the AAR file is generated prior to copying. */ -task copyReleaseAAR(type: Copy) { +task copyDebugAARToBin(type: Copy) { + dependsOn ':lib:assembleDebug' + from('lib/build/outputs/aar') + into(binDir) + include('godot-lib.debug.aar') +} + +/** + * Copy the Godot android library archive release file into the app module release libs directory. + * Depends on the library build task to ensure the AAR file is generated prior to copying. + */ +task copyReleaseAARToAppModule(type: Copy) { dependsOn ':lib:assembleRelease' from('lib/build/outputs/aar') into('app/libs/release') include('godot-lib.release.aar') } +task copyGodotPaymentPluginToAppModule(type: Copy) { + dependsOn ':plugins:godotpayment:assembleRelease' + from('plugins/godotpayment/build/outputs/aar') + into('app/libs/plugins') + include('GodotPayment.release.aar') +} + +/** + * Copy the Godot android library archive release file into the root bin directory. + * Depends on the library build task to ensure the AAR file is generated prior to copying. + */ +task copyReleaseAARToBin(type: Copy) { + dependsOn ':lib:assembleRelease' + from('lib/build/outputs/aar') + into(binDir) + include('godot-lib.release.aar') +} + /** * Generate Godot custom build template by zipping the source files from the app directory, as well * as the AAR files generated by 'copyDebugAAR' and 'copyReleaseAAR'. @@ -95,7 +125,7 @@ task zipCustomBuild(type: Zip) { 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: ['gradle.properties', 'gradlew', 'gradlew.bat', 'gradle/**'])) include '**/*' archiveName 'android_source.zip' destinationDir(file(binDir)) @@ -110,19 +140,24 @@ task generateGodotTemplates(type: GradleBuild) { startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType) } - tasks = [] + tasks = ["copyGodotPaymentPluginToAppModule"] // Only build the apks and aar files for which we have native shared libraries. for (String target : supportedTargets.keySet()) { File targetLibs = new File("lib/libs/" + target) - if (targetLibs != null && targetLibs.isDirectory()) { - File[] targetLibsContents = targetLibs.listFiles() - if (targetLibsContents != null && targetLibsContents.length > 0) { - // Copy the generated aar library files to the custom build directory. - tasks += "copy" + target.capitalize() + "AAR" - // Copy the prebuilt binary templates to the bin directory. - tasks += "copy" + target.capitalize() + "BinaryToBin" - } + if (targetLibs != null + && targetLibs.isDirectory() + && targetLibs.listFiles() != null + && targetLibs.listFiles().length > 0) { + String capitalizedTarget = target.capitalize() + // Copy the generated aar library files to the custom build directory. + tasks += "copy" + capitalizedTarget + "AARToAppModule" + // Copy the generated aar library files to the bin directory. + tasks += "copy" + capitalizedTarget + "AARToBin" + // Copy the prebuilt binary templates to the bin directory. + tasks += "copy" + capitalizedTarget + "BinaryToBin" + } else { + logger.lifecycle("No native shared libs for target $target. Skipping build.") } } @@ -133,20 +168,28 @@ task generateGodotTemplates(type: GradleBuild) { * Clean the generated artifacts. */ task cleanGodotTemplates(type: Delete) { - // Delete the generated native libs - delete("lib/libs") + // Delete the generated native libs + delete("lib/libs") + + // Delete the library generated AAR files + delete("lib/build/outputs/aar") + + // Delete the godotpayment libs directory contents + delete("plugins/godotpayment/libs") - // Delete the library generated AAR files - delete("lib/build/outputs/aar") + // Delete the generated godotpayment aar + delete("plugins/godotpayment/build/outputs/aar") - // Delete the app libs directory contents - delete("app/libs") + // Delete the app libs directory contents + delete("app/libs") - // Delete the generated binary apks - delete("app/build/outputs/apk") + // Delete the generated binary apks + delete("app/build/outputs/apk") - // Delete the Godot templates in the Godot bin directory - delete("$binDir/android_debug.apk") - delete("$binDir/android_release.apk") - delete("$binDir/android_source.zip") + // Delete the Godot templates in the Godot bin directory + delete("$binDir/android_debug.apk") + delete("$binDir/android_release.apk") + delete("$binDir/android_source.zip") + delete("$binDir/godot-lib.debug.aar") + delete("$binDir/godot-lib.release.aar") } diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties index bf50265715..f56b0f6a5e 100644 --- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties +++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index eb97484b9c..ca8aaf8af0 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -2,6 +2,8 @@ apply plugin: 'com.android.library' dependencies { implementation libraries.supportCoreUtils + implementation libraries.kotlinStdLib + implementation libraries.v4Support } def pathToRootDir = "../../../../" 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 021214b627..7db4aa6597 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -61,8 +61,11 @@ import android.os.Messenger; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings.Secure; +import android.support.annotation.CallSuper; import android.support.annotation.Keep; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.FragmentActivity; import android.view.Display; import android.view.KeyEvent; import android.view.MotionEvent; @@ -87,22 +90,20 @@ import com.google.android.vending.expansion.downloader.IStub; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; -import java.lang.reflect.Method; import java.security.MessageDigest; -import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Locale; -import javax.microedition.khronos.opengles.GL10; import org.godotengine.godot.input.GodotEditText; import org.godotengine.godot.payments.PaymentsManager; +import org.godotengine.godot.plugin.GodotPlugin; +import org.godotengine.godot.plugin.GodotPluginRegistry; import org.godotengine.godot.utils.GodotNetUtils; import org.godotengine.godot.utils.PermissionsUtil; import org.godotengine.godot.xr.XRMode; -public abstract class Godot extends Activity implements SensorEventListener, IDownloaderClient { +public abstract class Godot extends FragmentActivity implements SensorEventListener, IDownloaderClient { - static final int MAX_SINGLETONS = 64; private IStub mDownloaderClientStub; private TextView mStatusText; private TextView mProgressFraction; @@ -126,8 +127,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo private boolean activityResumed; private int mState; - // Used to dispatch events to the main thread. - private final Handler mainThreadHandler = new Handler(Looper.getMainLooper()); + private GodotPluginRegistry pluginRegistry; static private Intent mCurrentIntent; @@ -154,83 +154,6 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo mPauseButton.setText(stringResourceID); } - static public class SingletonBase { - - protected void registerClass(String p_name, String[] p_methods) { - - GodotLib.singleton(p_name, this); - - Class clazz = getClass(); - Method[] methods = clazz.getDeclaredMethods(); - for (Method method : methods) { - boolean found = false; - - for (String s : p_methods) { - if (s.equals(method.getName())) { - found = true; - break; - } - } - if (!found) - continue; - - List<String> ptr = new ArrayList<String>(); - - Class[] paramTypes = method.getParameterTypes(); - for (Class c : paramTypes) { - ptr.add(c.getName()); - } - - String[] pt = new String[ptr.size()]; - ptr.toArray(pt); - - GodotLib.method(p_name, method.getName(), method.getReturnType().getName(), pt); - } - - Godot.singletons[Godot.singleton_count++] = this; - } - - /** - * Invoked once during the Godot Android initialization process after creation of the - * {@link GodotView} view. - * <p> - * This method should be overridden by descendants of this class that would like to add - * their view/layout to the Godot view hierarchy. - * - * @return the view to be included; null if no views should be included. - */ - @Nullable - protected View onMainCreateView(Activity activity) { - return null; - } - - protected void onMainActivityResult(int requestCode, int resultCode, Intent data) { - } - - protected void onMainRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - } - - protected void onMainPause() {} - protected void onMainResume() {} - protected void onMainDestroy() {} - protected boolean onMainBackPressed() { return false; } - - protected void onGLDrawFrame(GL10 gl) {} - protected void onGLSurfaceChanged(GL10 gl, int width, int height) {} // singletons will always miss first onGLSurfaceChanged call - //protected void onGLSurfaceCreated(GL10 gl, EGLConfig config) {} // singletons won't be ready until first GodotLib.step() - - public void registerMethods() {} - } - - /* - protected List<SingletonBase> singletons = new ArrayList<SingletonBase>(); - protected void instanceSingleton(SingletonBase s) { - - s.registerMethods(); - singletons.add(s); - } - */ - private String[] command_line; private boolean use_apk_expansion; @@ -246,9 +169,6 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo public static GodotIO io; public static GodotNetUtils netUtils; - static SingletonBase[] singletons = new SingletonBase[MAX_SINGLETONS]; - static int singleton_count = 0; - public interface ResultCallback { public void callback(int requestCode, int resultCode, Intent data); } @@ -265,16 +185,15 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo result_callback = null; }; - for (int i = 0; i < singleton_count; i++) { - - singletons[i].onMainActivityResult(requestCode, resultCode, data); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onMainActivityResult(requestCode, resultCode, data); } }; @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - for (int i = 0; i < singleton_count; i++) { - singletons[i].onMainRequestPermissionsResult(requestCode, permissions, grantResults); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onMainRequestPermissionsResult(requestCode, permissions, grantResults); } for (int i = 0; i < permissions.length; i++) { @@ -283,6 +202,16 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo }; /** + * Invoked on the GL thread when the Godot main loop has started. + */ + @CallSuper + protected void onGLGodotMainLoopStarted() { + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGLGodotMainLoopStarted(); + } + } + + /** * Used by the native code (java_godot_lib_jni.cpp) to complete initialization of the GLSurfaceView view and renderer. */ @Keep @@ -302,14 +231,13 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo edittext.setView(mView); io.setEdit(edittext); - final Godot godot = this; mView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { Point fullSize = new Point(); - godot.getWindowManager().getDefaultDisplay().getSize(fullSize); + getWindowManager().getDefaultDisplay().getSize(fullSize); Rect gameSize = new Rect(); - godot.mView.getWindowVisibleDisplayFrame(gameSize); + mView.getWindowVisibleDisplayFrame(gameSize); final int keyboardHeight = fullSize.y - gameSize.bottom; GodotLib.setVirtualKeyboardHeight(keyboardHeight); @@ -321,23 +249,23 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo @Override public void run() { GodotLib.setup(current_command_line); - setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))); - // The Godot Android plugins are setup on completion of GodotLib.setup - mainThreadHandler.post(new Runnable() { - @Override - public void run() { - // Include the non-null views returned in the Godot view hierarchy. - for (int i = 0; i < singleton_count; i++) { - View view = singletons[i].onMainCreateView(Godot.this); - if (view != null) { - layout.addView(view); - } - } - } - }); + // Must occur after GodotLib.setup has completed. + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGLRegisterPluginWithGodotNative(); + } + + 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.onMainCreateView(this); + if (pluginView != null) { + layout.addView(pluginView); + } + } } public void setKeepScreenOn(final boolean p_enabled) { @@ -535,6 +463,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); mClipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); + pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this); //check for apk expansion API if (true) { @@ -675,8 +604,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo protected void onDestroy() { if (mPaymentsManager != null) mPaymentsManager.destroy(); - for (int i = 0; i < singleton_count; i++) { - singletons[i].onMainDestroy(); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onMainDestroy(); } GodotLib.ondestroy(this); @@ -703,8 +632,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo mSensorManager.unregisterListener(this); - for (int i = 0; i < singleton_count; i++) { - singletons[i].onMainPause(); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onMainPause(); } } @@ -755,9 +684,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); } - for (int i = 0; i < singleton_count; i++) { - - singletons[i].onMainResume(); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onMainResume(); } } @@ -850,8 +778,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo public void onBackPressed() { boolean shouldQuit = true; - for (int i = 0; i < singleton_count; i++) { - if (singletons[i].onMainBackPressed()) { + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + if (plugin.onMainBackPressed()) { shouldQuit = false; } } @@ -866,6 +794,17 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo } } + /** + * Queue a runnable to be run on the GL thread. + * <p> + * This must be called after the GL thread has started. + */ + public final void runOnGLThread(@NonNull Runnable action) { + if (mView != null) { + mView.queueEvent(action); + } + } + private void forceQuit() { System.exit(0); } @@ -990,8 +929,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo int keyCode; if ((keyCode = cc[i]) != 0) { // Simulate key down and up... - GodotLib.key(0, keyCode, true); - GodotLib.key(0, keyCode, false); + GodotLib.key(0, 0, keyCode, true); + GodotLib.key(0, 0, keyCode, false); } } } 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 e0b46673ba..89a65aea24 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -136,7 +136,7 @@ public class GodotLib { /** * Forward regular key events from the main thread to the GL thread. */ - public static native void key(int p_scancode, int p_unicode_char, boolean p_pressed); + public static native void key(int p_keycode, int p_scancode, int p_unicode_char, boolean p_pressed); /** * Forward game device's key events from the main thread to the GL thread. @@ -176,22 +176,6 @@ public class GodotLib { public static native void audio(); /** - * Used to setup a {@link org.godotengine.godot.Godot.SingletonBase} instance. - * @param p_name Name of the instance. - * @param p_object Reference to the singleton instance. - */ - public static native void singleton(String p_name, Object p_object); - - /** - * Used to complete registration of the {@link org.godotengine.godot.Godot.SingletonBase} instance's methods. - * @param p_sname Name of the instance - * @param p_name Name of the method to register - * @param p_ret Return type of the registered method - * @param p_params Method parameters types - */ - public static native void method(String p_sname, String p_name, String p_ret, String[] p_params); - - /** * 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/GodotRenderer.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java index 26fa033f12..ee9a2aee4f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java @@ -30,9 +30,12 @@ package org.godotengine.godot; +import android.content.Context; import android.opengl.GLSurfaceView; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; +import org.godotengine.godot.plugin.GodotPlugin; +import org.godotengine.godot.plugin.GodotPluginRegistry; import org.godotengine.godot.utils.GLUtils; /** @@ -40,8 +43,13 @@ import org.godotengine.godot.utils.GLUtils; */ class GodotRenderer implements GLSurfaceView.Renderer { + private final GodotPluginRegistry pluginRegistry; private boolean activityJustResumed = false; + GodotRenderer() { + this.pluginRegistry = GodotPluginRegistry.getPluginRegistry(); + } + public void onDrawFrame(GL10 gl) { if (activityJustResumed) { GodotLib.onRendererResumed(); @@ -49,21 +57,23 @@ class GodotRenderer implements GLSurfaceView.Renderer { } GodotLib.step(); - for (int i = 0; i < Godot.singleton_count; i++) { - Godot.singletons[i].onGLDrawFrame(gl); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGLDrawFrame(gl); } } public void onSurfaceChanged(GL10 gl, int width, int height) { - GodotLib.resize(width, height); - for (int i = 0; i < Godot.singleton_count; i++) { - Godot.singletons[i].onGLSurfaceChanged(gl, width, height); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGLSurfaceChanged(gl, width, height); } } public void onSurfaceCreated(GL10 gl, EGLConfig config) { GodotLib.newcontext(GLUtils.use_32); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGLSurfaceCreated(gl, config); + } } void onActivityResumed() { 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 b2b88088e8..e00ca86c41 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 @@ -98,11 +98,12 @@ public class GodotInputHandler implements InputDeviceListener { }); } } else { + final int scanCode = event.getScanCode(); final int chr = event.getUnicodeChar(0); queueEvent(new Runnable() { @Override public void run() { - GodotLib.key(keyCode, chr, false); + GodotLib.key(keyCode, scanCode, chr, false); } }); }; @@ -143,11 +144,12 @@ public class GodotInputHandler implements InputDeviceListener { }); } } else { + final int scanCode = event.getScanCode(); final int chr = event.getUnicodeChar(0); queueEvent(new Runnable() { @Override public void run() { - GodotLib.key(keyCode, chr, true); + GodotLib.key(keyCode, scanCode, chr, true); } }); }; 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 3a154f1bf3..8d9b5461a1 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 @@ -91,8 +91,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene @Override public void run() { for (int i = 0; i < count; ++i) { - GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true); - GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false); + GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, true); + GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, false); } } }); @@ -110,8 +110,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene @Override public void run() { for (int i = 0; i < count; ++i) { - GodotLib.key(0, newChars[i], true); - GodotLib.key(0, newChars[i], false); + GodotLib.key(0, 0, newChars[i], true); + GodotLib.key(0, 0, newChars[i], false); } } }); @@ -127,8 +127,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene public void run() { for (int i = 0; i < characters.length(); i++) { final int ch = characters.codePointAt(i); - GodotLib.key(0, ch, true); - GodotLib.key(0, ch, false); + GodotLib.key(0, 0, ch, true); + GodotLib.key(0, 0, ch, false); } } }); diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/GodotPaymentInterface.java b/platform/android/java/lib/src/org/godotengine/godot/payments/GodotPaymentInterface.java new file mode 100644 index 0000000000..6ac7338b30 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/payments/GodotPaymentInterface.java @@ -0,0 +1,97 @@ +/*************************************************************************/ +/* GodotPaymentInterface.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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.payments; + +public interface GodotPaymentInterface { + void purchase(String sku, String transactionId); + + void consumeUnconsumedPurchases(); + + String getSignature(); + + void callbackSuccess(String ticket, String signature, String sku); + + void callbackSuccessProductMassConsumed(String ticket, String signature, String sku); + + void callbackSuccessNoUnconsumedPurchases(); + + void callbackFailConsume(String message); + + void callbackFail(String message); + + void callbackCancel(); + + void callbackAlreadyOwned(String sku); + + int getPurchaseCallbackId(); + + void setPurchaseCallbackId(int purchaseCallbackId); + + String getPurchaseValidationUrlPrefix(); + + void setPurchaseValidationUrlPrefix(String url); + + String getAccessToken(); + + void setAccessToken(String accessToken); + + void setTransactionId(String transactionId); + + String getTransactionId(); + + // request purchased items are not consumed + void requestPurchased(); + + // callback for requestPurchased() + void callbackPurchased(String receipt, String signature, String sku); + + void callbackDisconnected(); + + void callbackConnected(); + + // true if connected, false otherwise + boolean isConnected(); + + // consume item automatically after purchase. default is true. + void setAutoConsume(boolean autoConsume); + + // consume a specific item + void consume(String sku); + + // query in app item detail info + void querySkuDetails(String[] list); + + void addSkuDetail(String itemJson); + + void completeSkuDetail(); + + void errorSkuDetail(String errorMessage); +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java b/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java index 90b958266b..9bf6650f84 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java +++ b/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java @@ -43,7 +43,6 @@ import android.util.Log; import com.android.vending.billing.IInAppBillingService; import java.util.ArrayList; import java.util.Arrays; -import org.godotengine.godot.GodotPaymentV3; import org.json.JSONException; import org.json.JSONObject; @@ -90,9 +89,9 @@ public class PaymentsManager { public void onServiceDisconnected(ComponentName name) { mService = null; - // At this stage, godotPaymentV3 might not have been initialized yet. - if (godotPaymentV3 != null) { - godotPaymentV3.callbackDisconnected(); + // At this stage, godotPayment might not have been initialized yet. + if (godotPayment != null) { + godotPayment.callbackDisconnected(); } } @@ -100,9 +99,9 @@ public class PaymentsManager { public void onServiceConnected(ComponentName name, IBinder service) { mService = IInAppBillingService.Stub.asInterface(service); - // At this stage, godotPaymentV3 might not have been initialized yet. - if (godotPaymentV3 != null) { - godotPaymentV3.callbackConnected(); + // At this stage, godotPayment might not have been initialized yet. + if (godotPayment != null) { + godotPayment.callbackConnected(); } } }; @@ -111,17 +110,17 @@ public class PaymentsManager { new PurchaseTask(mService, activity) { @Override protected void error(String message) { - godotPaymentV3.callbackFail(message); + godotPayment.callbackFail(message); } @Override protected void canceled() { - godotPaymentV3.callbackCancel(); + godotPayment.callbackCancel(); } @Override protected void alreadyOwned() { - godotPaymentV3.callbackAlreadyOwned(sku); + godotPayment.callbackAlreadyOwned(sku); } } .purchase(sku, transactionId); @@ -135,19 +134,19 @@ public class PaymentsManager { new ReleaseAllConsumablesTask(mService, activity) { @Override protected void success(String sku, String receipt, String signature, String token) { - godotPaymentV3.callbackSuccessProductMassConsumed(receipt, signature, sku); + godotPayment.callbackSuccessProductMassConsumed(receipt, signature, sku); } @Override protected void error(String message) { Log.d("godot", "consumeUnconsumedPurchases :" + message); - godotPaymentV3.callbackFailConsume(message); + godotPayment.callbackFailConsume(message); } @Override protected void notRequired() { Log.d("godot", "callbackSuccessNoUnconsumedPurchases :"); - godotPaymentV3.callbackSuccessNoUnconsumedPurchases(); + godotPayment.callbackSuccessNoUnconsumedPurchases(); } } .consumeItAll(); @@ -168,7 +167,7 @@ public class PaymentsManager { final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST"); if (myPurchases == null || myPurchases.size() == 0) { - godotPaymentV3.callbackPurchased("", "", ""); + godotPayment.callbackPurchased("", "", ""); return; } @@ -186,7 +185,7 @@ public class PaymentsManager { pc.setConsumableFlag("block", sku, true); pc.setConsumableValue("token", sku, token); - godotPaymentV3.callbackPurchased(receipt, signature, sku); + godotPayment.callbackPurchased(receipt, signature, sku); } catch (JSONException e) { } } @@ -203,7 +202,7 @@ public class PaymentsManager { new HandlePurchaseTask(activity) { @Override protected void success(final String sku, final String signature, final String ticket) { - godotPaymentV3.callbackSuccess(ticket, signature, sku); + godotPayment.callbackSuccess(ticket, signature, sku); if (auto_consume) { new ConsumeTask(mService, activity) { @@ -213,7 +212,7 @@ public class PaymentsManager { @Override protected void error(String message) { - godotPaymentV3.callbackFail(message); + godotPayment.callbackFail(message); } } .consume(sku); @@ -222,12 +221,12 @@ public class PaymentsManager { @Override protected void error(String message) { - godotPaymentV3.callbackFail(message); + godotPayment.callbackFail(message); } @Override protected void canceled() { - godotPaymentV3.callbackCancel(); + godotPayment.callbackCancel(); } } .handlePurchaseRequest(resultCode, data); @@ -235,19 +234,19 @@ public class PaymentsManager { public void validatePurchase(String purchaseToken, final String sku) { - new ValidateTask(activity, godotPaymentV3) { + new ValidateTask(activity, godotPayment) { @Override protected void success() { new ConsumeTask(mService, activity) { @Override protected void success(String ticket) { - godotPaymentV3.callbackSuccess(ticket, null, sku); + godotPayment.callbackSuccess(ticket, null, sku); } @Override protected void error(String message) { - godotPaymentV3.callbackFail(message); + godotPayment.callbackFail(message); } } .consume(sku); @@ -255,12 +254,12 @@ public class PaymentsManager { @Override protected void error(String message) { - godotPaymentV3.callbackFail(message); + godotPayment.callbackFail(message); } @Override protected void canceled() { - godotPaymentV3.callbackCancel(); + godotPayment.callbackCancel(); } } .validatePurchase(sku); @@ -274,12 +273,12 @@ public class PaymentsManager { new ConsumeTask(mService, activity) { @Override protected void success(String ticket) { - godotPaymentV3.callbackSuccessProductMassConsumed(ticket, "", sku); + godotPayment.callbackSuccessProductMassConsumed(ticket, "", sku); } @Override protected void error(String message) { - godotPaymentV3.callbackFailConsume(message); + godotPayment.callbackFailConsume(message); } } .consume(sku); @@ -387,9 +386,9 @@ public class PaymentsManager { if (!skuDetails.containsKey("DETAILS_LIST")) { int response = getResponseCodeFromBundle(skuDetails); if (response != BILLING_RESPONSE_RESULT_OK) { - godotPaymentV3.errorSkuDetail(getResponseDesc(response)); + godotPayment.errorSkuDetail(getResponseDesc(response)); } else { - godotPaymentV3.errorSkuDetail("No error but no detail list."); + godotPayment.errorSkuDetail("No error but no detail list."); } return; } @@ -398,22 +397,22 @@ public class PaymentsManager { for (String thisResponse : responseList) { Log.d("godot", "response = " + thisResponse); - godotPaymentV3.addSkuDetail(thisResponse); + godotPayment.addSkuDetail(thisResponse); } } catch (RemoteException e) { e.printStackTrace(); - godotPaymentV3.errorSkuDetail("RemoteException error!"); + godotPayment.errorSkuDetail("RemoteException error!"); } } - godotPaymentV3.completeSkuDetail(); + godotPayment.completeSkuDetail(); } })) .start(); } - private GodotPaymentV3 godotPaymentV3; + private GodotPaymentInterface godotPayment; - public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) { - this.godotPaymentV3 = godotPaymentV3; + public void setBaseSingleton(GodotPaymentInterface godotPaymentInterface) { + this.godotPayment = godotPaymentInterface; } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java index dbb6b8a783..10c314aecf 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java +++ b/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java @@ -34,7 +34,6 @@ import android.app.Activity; import android.app.ProgressDialog; import android.os.AsyncTask; import java.lang.ref.WeakReference; -import org.godotengine.godot.GodotPaymentV3; import org.godotengine.godot.utils.HttpRequester; import org.godotengine.godot.utils.RequestParams; import org.json.JSONException; @@ -43,7 +42,7 @@ import org.json.JSONObject; abstract public class ValidateTask { private Activity context; - private GodotPaymentV3 godotPaymentsV3; + private GodotPaymentInterface godotPayments; private ProgressDialog dialog; private String mSku; @@ -80,9 +79,9 @@ abstract public class ValidateTask { } } - public ValidateTask(Activity context, GodotPaymentV3 godotPaymentsV3) { + public ValidateTask(Activity context, GodotPaymentInterface godotPayments) { this.context = context; - this.godotPaymentsV3 = godotPaymentsV3; + this.godotPayments = godotPayments; } public void validatePurchase(final String sku) { @@ -96,7 +95,7 @@ abstract public class ValidateTask { private String doInBackground(String... params) { PaymentsCache pc = new PaymentsCache(context); - String url = godotPaymentsV3.getPurchaseValidationUrlPrefix(); + String url = godotPayments.getPurchaseValidationUrlPrefix(); RequestParams param = new RequestParams(); param.setUrl(url); param.put("ticket", pc.getConsumableValue("ticket", mSku)); 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 new file mode 100644 index 0000000000..d5bf4fc70e --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java @@ -0,0 +1,256 @@ +/*************************************************************************/ +/* GodotPlugin.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 android.app.Activity; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; +import org.godotengine.godot.Godot; + +/** + * Base class for the Godot Android plugins. + * <p> + * A Godot Android plugin is a regular Android library packaged as an aar archive file with the following caveats: + * <p> + * - The library must have a dependency on the Godot Android library (godot-lib.aar). + * A stable version is available for each release. + * <p> + * - The library must include a <meta-data> tag in its manifest file setup as follow: + * <meta-data android:name="org.godotengine.plugin.v1.[PluginName]" android:value="[plugin.init.ClassFullName]" /> + * Where: + * - 'PluginName' is the name of the plugin. + * - 'plugin.init.ClassFullName' is the full name (package + class name) of the plugin class + * extending {@link GodotPlugin}. + * + * A plugin can also define and provide c/c++ gdnative libraries and nativescripts for the target + * app/game to leverage. + * The shared library for the gdnative library will be automatically bundled by the aar build + * system. + * Godot '*.gdnlib' and '*.gdns' resource files must however be manually defined in the project + * 'assets' directory. The recommended path for these resources in the 'assets' directory should be: + * 'godot/plugin/v1/[PluginName]/' + */ +public abstract class GodotPlugin { + + private final Godot godot; + + public GodotPlugin(Godot godot) { + this.godot = godot; + } + + /** + * Provides access to the Godot engine. + */ + protected Godot getGodot() { + return godot; + } + + /** + * Register the plugin with Godot native code. + */ + public final void onGLRegisterPluginWithGodotNative() { + nativeRegisterSingleton(getPluginName()); + + 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; + } + } + if (!found) + continue; + + List<String> ptr = new ArrayList<String>(); + + 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); + } + + // Get the list of gdnative libraries to register. + Set<String> gdnativeLibrariesPaths = getPluginGDNativeLibrariesPaths(); + if (!gdnativeLibrariesPaths.isEmpty()) { + nativeRegisterGDNativeLibraries(gdnativeLibrariesPaths.toArray(new String[0])); + } + } + + /** + * Invoked once during the Godot Android initialization process after creation of the + * {@link org.godotengine.godot.GodotView} view. + * <p> + * This method should be overridden by descendants of this class that would like to add + * their view/layout to the Godot view hierarchy. + * + * @return the view to be included; null if no views should be included. + */ + @Nullable + public View onMainCreateView(Activity activity) { + return null; + } + + /** + * @see Activity#onActivityResult(int, int, Intent) + */ + public void onMainActivityResult(int requestCode, int resultCode, Intent data) { + } + + /** + * @see Activity#onRequestPermissionsResult(int, String[], int[]) + */ + public void onMainRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + } + + /** + * @see Activity#onPause() + */ + public void onMainPause() {} + + /** + * @see Activity#onResume() + */ + public void onMainResume() {} + + /** + * @see Activity#onDestroy() + */ + public void onMainDestroy() {} + + /** + * @see Activity#onBackPressed() + */ + public boolean onMainBackPressed() { return false; } + + /** + * Invoked on the GL thread when the Godot main loop has started. + */ + public void onGLGodotMainLoopStarted() {} + + /** + * Invoked once per frame on the GL thread after the frame is drawn. + */ + public void onGLDrawFrame(GL10 gl) {} + + /** + * Called on the GL thread after the surface is created and whenever the OpenGL ES surface size + * changes. + */ + public void onGLSurfaceChanged(GL10 gl, int width, int height) {} + + /** + * Called on the GL thread when the surface is created or recreated. + */ + public void onGLSurfaceCreated(GL10 gl, EGLConfig config) {} + + /** + * Returns the name of the plugin. + * <p> + * This value must match the one listed in the plugin '<meta-data>' manifest entry. + */ + @NonNull + public abstract String getPluginName(); + + /** + * Returns the list of methods to be exposed to Godot. + */ + @NonNull + public abstract List<String> getPluginMethods(); + + /** + * Returns the paths for the plugin's gdnative libraries. + * + * The paths must be relative to the 'assets' directory and point to a '*.gdnlib' file. + */ + @NonNull + protected Set<String> getPluginGDNativeLibrariesPaths() { + return Collections.emptySet(); + } + + /** + * 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. + * + * @param action the action to run on the UI thread + */ + protected void runOnUiThread(Runnable action) { + godot.runOnUiThread(action); + } + + /** + * Queue the specified action to be run on the GL thread. + * + * @param action the action to run on the GL thread + */ + protected void runOnGLThread(Runnable action) { + godot.runOnGLThread(action); + } + + /** + * Used to setup a {@link GodotPlugin} instance. + * @param p_name Name of the instance. + */ + private native void nativeRegisterSingleton(String p_name); + + /** + * Used to complete registration of the {@link GodotPlugin} instance's methods. + * @param p_sname Name of the instance + * @param p_name Name of the method to register + * @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); + + /** + * 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); +} 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 new file mode 100644 index 0000000000..b6d949b7bf --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java @@ -0,0 +1,199 @@ +/*************************************************************************/ +/* GodotPluginRegistry.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import org.godotengine.godot.Godot; + +/** + * Registry used to load and access the registered Godot Android plugins. + */ +public final class GodotPluginRegistry { + + private static final String TAG = GodotPluginRegistry.class.getSimpleName(); + + 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 = "custom_template_plugins"; + + private static GodotPluginRegistry instance; + private final ConcurrentHashMap<String, GodotPlugin> registry; + + private GodotPluginRegistry(Godot godot) { + registry = new ConcurrentHashMap<>(); + loadPlugins(godot); + } + + /** + * Retrieve the plugin tied to the given plugin name. + * @param pluginName Name of the plugin + * @return {@link GodotPlugin} handle if it exists, null otherwise. + */ + @Nullable + public GodotPlugin getPlugin(String pluginName) { + return registry.get(pluginName); + } + + /** + * Retrieve the full set of loaded plugins. + */ + public Collection<GodotPlugin> getAllPlugins() { + return registry.values(); + } + + /** + * Parse the manifest file and load all included Godot Android plugins. + * <p> + * A plugin manifest entry is a '<meta-data>' tag setup as described in the {@link GodotPlugin} + * documentation. + * + * @param godot Godot instance + * @return A singleton instance of {@link GodotPluginRegistry}. This ensures that only one instance + * of each Godot Android plugins is available at runtime. + */ + public static GodotPluginRegistry initializePluginRegistry(Godot godot) { + if (instance == null) { + instance = new GodotPluginRegistry(godot); + } + + return instance; + } + + /** + * Return the plugin registry if it's initialized. + * Throws a {@link IllegalStateException} exception if not. + * + * @throws IllegalStateException if {@link GodotPluginRegistry#initializePluginRegistry(Godot)} has not been called prior to calling this method. + */ + public static GodotPluginRegistry getPluginRegistry() throws IllegalStateException { + if (instance == null) { + throw new IllegalStateException("Plugin registry hasn't been initialized."); + } + + return instance; + } + + private void loadPlugins(Godot godot) { + try { + ApplicationInfo appInfo = godot + .getPackageManager() + .getApplicationInfo(godot.getPackageName(), PackageManager.GET_META_DATA); + Bundle metaData = appInfo.metaData; + if (metaData == null || metaData.isEmpty()) { + return; + } + + // When using the Godot editor for building and exporting the apk, this is used to check + // which plugins to enable since the custom build template may contain prebuilt plugins. + // 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(","); + 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; + } + + // Retrieve the plugin class full name. + String pluginHandleClassFullName = metaData.getString(metaDataName); + if (!TextUtils.isEmpty(pluginHandleClassFullName)) { + try { + // Attempt to create the plugin init class via reflection. + @SuppressWarnings("unchecked") + Class<GodotPlugin> pluginClass = (Class<GodotPlugin>)Class + .forName(pluginHandleClassFullName); + Constructor<GodotPlugin> pluginConstructor = pluginClass + .getConstructor(Godot.class); + GodotPlugin pluginHandle = pluginConstructor.newInstance(godot); + + // 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()); + } + registry.put(pluginName, pluginHandle); + } catch (ClassNotFoundException e) { + Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); + } catch (IllegalAccessException e) { + Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); + } catch (InstantiationException e) { + Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); + } catch (NoSuchMethodException e) { + Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); + } catch (InvocationTargetException e) { + Log.w(TAG, "Unable to load Godot plugin " + pluginName, e); + } + } else { + Log.w(TAG, "Invalid plugin loader class for " + pluginName); + } + } + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable load Godot Android plugins from the manifest file.", e); + } + } +} 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 new file mode 100644 index 0000000000..67faad8ddd --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt @@ -0,0 +1,99 @@ +/*************************************************************************/ +/* VkRenderer.kt */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +@file:JvmName("VkRenderer") +package org.godotengine.godot.vulkan + +import android.view.Surface + +/** + * Responsible to setting up and driving the Vulkan rendering logic. + * + * <h3>Threading</h3> + * The renderer will be called on a separate thread, so that rendering + * performance is decoupled from the UI thread. Clients typically need to + * communicate with the renderer from the UI thread, because that's where + * input events are received. Clients can communicate using any of the + * standard Java techniques for cross-thread communication, or they can + * use the [VkSurfaceView.queueOnVkThread] convenience method. + * + * @see [VkSurfaceView.startRenderer] + */ +internal class VkRenderer { + + /** + * Called when the surface is created and signals the beginning of rendering. + */ + fun onVkSurfaceCreated(surface: Surface) { + nativeOnVkSurfaceCreated(surface) + } + + /** + * Called after the surface is created and whenever its size changes. + */ + fun onVkSurfaceChanged(surface: Surface, width: Int, height: Int) { + nativeOnVkSurfaceChanged(surface, width, height) + } + + /** + * Called to draw the current frame. + */ + fun onVkDrawFrame() { + nativeOnVkDrawFrame() + } + + /** + * Called when the rendering thread is resumed. + */ + fun onVkResume() { + nativeOnVkResume() + } + + /** + * Called when the rendering thread is paused. + */ + fun onVkPause() { + nativeOnVkPause() + } + + /** + * Called when the rendering thread is destroyed and used as signal to tear down the Vulkan logic. + */ + fun onVkDestroy() { + nativeOnVkDestroy() + } + + private external fun nativeOnVkSurfaceCreated(surface: Surface) + private external fun nativeOnVkSurfaceChanged(surface: Surface, width: Int, height: Int) + private external fun nativeOnVkResume() + private external fun nativeOnVkDrawFrame() + private external fun nativeOnVkPause() + private external fun nativeOnVkDestroy() +} 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 new file mode 100644 index 0000000000..1c594f3201 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt @@ -0,0 +1,136 @@ +/*************************************************************************/ +/* VkSurfaceView.kt */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +@file:JvmName("VkSurfaceView") +package org.godotengine.godot.vulkan + +import android.content.Context +import android.view.SurfaceHolder +import android.view.SurfaceView + +/** + * An implementation of SurfaceView that uses the dedicated surface for + * displaying Vulkan rendering. + * <p> + * A [VkSurfaceView] provides the following features: + * <p> + * <ul> + * <li>Manages a surface, which is a special piece of memory that can be + * composited into the Android view system. + * <li>Accepts a user-provided [VkRenderer] object that does the actual rendering. + * <li>Renders on a dedicated [VkThread] thread to decouple rendering performance from the + * UI thread. + * </ul> + */ +internal class VkSurfaceView(context: Context) : SurfaceView(context), SurfaceHolder.Callback { + + companion object { + fun checkState(expression: Boolean, errorMessage: Any) { + check(expression) { errorMessage.toString() } + } + } + + /** + * Thread used to drive the vulkan logic. + */ + private val vkThread: VkThread by lazy { + VkThread(this, renderer) + } + + /** + * Performs the actual rendering. + */ + private lateinit var renderer: VkRenderer + + init { + isClickable = true + holder.addCallback(this) + } + + /** + * Set the [VkRenderer] associated with the view, and starts the thread that will drive the vulkan + * rendering. + * + * This method should be called once and only once in the life-cycle of [VkSurfaceView]. + */ + fun startRenderer(renderer: VkRenderer) { + checkState(!this::renderer.isInitialized, "startRenderer must only be invoked once") + this.renderer = renderer + vkThread.start() + } + + /** + * Queues a runnable to be run on the Vulkan rendering thread. + * + * Must not be called before a [VkRenderer] has been set. + */ + fun queueOnVkThread(runnable: Runnable) { + vkThread.queueEvent(runnable) + } + + /** + * Resumes the rendering thread. + * + * Must not be called before a [VkRenderer] has been set. + */ + fun onResume() { + vkThread.onResume() + } + + /** + * Pauses the rendering thread. + * + * Must not be called before a [VkRenderer] has been set. + */ + fun onPause() { + vkThread.onPause() + } + + /** + * Tear down the rendering thread. + * + * Must not be called before a [VkRenderer] has been set. + */ + fun onDestroy() { + vkThread.blockingExit() + } + + override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { + vkThread.onSurfaceChanged(width, height) + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + vkThread.onSurfaceDestroyed() + } + + override fun surfaceCreated(holder: SurfaceHolder) { + vkThread.onSurfaceCreated() + } +} 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 new file mode 100644 index 0000000000..2e332840bf --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt @@ -0,0 +1,230 @@ +/*************************************************************************/ +/* VkThread.kt */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +@file:JvmName("VkThread") +package org.godotengine.godot.vulkan + +import android.util.Log +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +/** + * Thread implementation for the [VkSurfaceView] onto which the vulkan logic is ran. + * + * 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 + } + + /** + * Used to run events scheduled on the thread. + */ + private val eventQueue = ArrayList<Runnable>() + + /** + * Used to synchronize interaction with other threads (e.g: main thread). + */ + private val lock = ReentrantLock() + private val lockCondition = lock.newCondition() + + private var shouldExit = false + private var exited = false + private var rendererInitialized = false + private var rendererResumed = false + private var resumed = false + private var hasSurface = false + private var width = 0 + private var height = 0 + + /** + * Determine when drawing can occur on the thread. This usually occurs after the + * [android.view.Surface] is available, the app is in a resumed state. + */ + private val readyToDraw + get() = hasSurface && resumed + + private fun threadExiting() { + lock.withLock { + exited = true + lockCondition.signalAll() + } + } + + /** + * Queue an event on the [VkThread]. + */ + fun queueEvent(event: Runnable) { + lock.withLock { + eventQueue.add(event) + lockCondition.signalAll() + } + } + + /** + * Request the thread to exit and block until it's done. + */ + fun blockingExit() { + lock.withLock { + shouldExit = true + lockCondition.signalAll() + while (!exited) { + try { + Log.i(TAG, "Waiting on exit for $name") + lockCondition.await() + } catch (ex: InterruptedException) { + currentThread().interrupt() + } + } + } + } + + /** + * Invoked when the app resumes. + */ + fun onResume() { + lock.withLock { + resumed = true + lockCondition.signalAll() + } + } + + /** + * Invoked when the app pauses. + */ + fun onPause() { + lock.withLock { + resumed = false + lockCondition.signalAll() + } + } + + /** + * Invoked when the [android.view.Surface] has been created. + */ + fun onSurfaceCreated() { + // This is a no op because surface creation will always be followed by surfaceChanged() + // which provide all the needed information. + } + + /** + * Invoked following structural updates to [android.view.Surface]. + */ + fun onSurfaceChanged(width: Int, height: Int) { + lock.withLock { + hasSurface = true + this.width = width + this.height = height + lockCondition.signalAll() + } + } + + /** + * Invoked when the [android.view.Surface] is no longer available. + */ + fun onSurfaceDestroyed() { + lock.withLock { + hasSurface = false + lockCondition.signalAll() + } + } + + /** + * Thread loop modeled after [android.opengl.GLSurfaceView]'s GLThread. + */ + override fun run() { + try { + while (true) { + var event: Runnable? = null + lock.withLock { + while (true) { + // Code path for exiting the thread loop. + if (shouldExit) { + vkRenderer.onVkDestroy() + return + } + + // Check for events and execute them outside of the loop if found to avoid + // blocking the thread lifecycle by holding onto the lock. + if (eventQueue.isNotEmpty()) { + event = eventQueue.removeAt(0) + break; + } + + if (readyToDraw) { + if (!rendererResumed) { + rendererResumed = true + vkRenderer.onVkResume() + + if (!rendererInitialized) { + rendererInitialized = true + vkRenderer.onVkSurfaceCreated(vkSurfaceView.holder.surface) + } + + vkRenderer.onVkSurfaceChanged(vkSurfaceView.holder.surface, width, height) + } + + // Break out of the loop so drawing can occur without holding onto the lock. + break; + } else if (rendererResumed) { + // If we aren't ready to draw but are resumed, that means we either lost a surface + // or the app was paused. + rendererResumed = false + vkRenderer.onVkPause() + } + // We only reach this state if we are not ready to draw and have no queued events, so + // we wait. + // On state change, the thread will be awoken using the [lock] and [lockCondition], and + // we will resume execution. + lockCondition.await() + } + } + + // Run queued event. + if (event != null) { + event?.run() + continue + } + + // Draw only when there no more queued events. + vkRenderer.onVkDrawFrame() + } + } catch (ex: InterruptedException) { + Log.i(TAG, ex.message) + } catch (ex: IllegalStateException) { + Log.i(TAG, ex.message) + } finally { + threadExiting() + } + } + +} diff --git a/platform/android/java/plugins/godotpayment/build.gradle b/platform/android/java/plugins/godotpayment/build.gradle new file mode 100644 index 0000000000..4f376c4587 --- /dev/null +++ b/platform/android/java/plugins/godotpayment/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion versions.compileSdk + buildToolsVersion versions.buildTools + + defaultConfig { + minSdkVersion versions.minSdk + targetSdkVersion versions.targetSdk + } + + libraryVariants.all { variant -> + variant.outputs.all { output -> + output.outputFileName = "GodotPayment.${variant.name}.aar" + } + } + +} + +dependencies { + implementation libraries.supportCoreUtils + implementation libraries.v4Support + + if (rootProject.findProject(":lib")) { + compileOnly project(":lib") + } else if (rootProject.findProject(":godot:lib")) { + compileOnly project(":godot:lib") + } else { + compileOnly fileTree(dir: 'libs', include: ['godot-lib*.aar']) + } +} diff --git a/platform/android/java/plugins/godotpayment/src/main/AndroidManifest.xml b/platform/android/java/plugins/godotpayment/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..61afa03799 --- /dev/null +++ b/platform/android/java/plugins/godotpayment/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.godotengine.godot.plugin.payment"> + + <application> + + <meta-data + android:name="org.godotengine.plugin.v1.GodotPayment" + android:value="org.godotengine.godot.plugin.payment.GodotPayment" /> + + </application> +</manifest> diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java index 93265d509f..4a6b611c4d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java +++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java @@ -1,5 +1,5 @@ /*************************************************************************/ -/* GodotPaymentV3.java */ +/* GodotPayment.java */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,20 +28,24 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot; +package org.godotengine.godot.plugin.payment; -import android.app.Activity; +import android.support.annotation.NonNull; import android.util.Log; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.godotengine.godot.Dictionary; +import org.godotengine.godot.Godot; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.payments.GodotPaymentInterface; import org.godotengine.godot.payments.PaymentsManager; +import org.godotengine.godot.plugin.GodotPlugin; import org.json.JSONException; import org.json.JSONObject; -public class GodotPaymentV3 extends Godot.SingletonBase { +public class GodotPayment extends GodotPlugin implements GodotPaymentInterface { - private Godot activity; private Integer purchaseCallbackId = 0; private String accessToken; private String purchaseValidationUrlPrefix; @@ -49,8 +53,15 @@ public class GodotPaymentV3 extends Godot.SingletonBase { private PaymentsManager mPaymentManager; private Dictionary mSkuDetails = new Dictionary(); + public GodotPayment(Godot godot) { + super(godot); + mPaymentManager = godot.getPaymentsManager(); + mPaymentManager.setBaseSingleton(this); + } + + @Override public void purchase(final String sku, final String transactionId) { - activity.runOnUiThread(new Runnable() { + runOnUiThread(new Runnable() { @Override public void run() { mPaymentManager.requestPurchase(sku, transactionId); @@ -58,21 +69,9 @@ public class GodotPaymentV3 extends Godot.SingletonBase { }); } - static public Godot.SingletonBase initialize(Activity p_activity) { - - return new GodotPaymentV3(p_activity); - } - - public GodotPaymentV3(Activity p_activity) { - - registerClass("GodotPayments", new String[] { "purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", "setAutoConsume", "consume", "querySkuDetails", "isConnected" }); - activity = (Godot)p_activity; - mPaymentManager = activity.getPaymentsManager(); - mPaymentManager.setBaseSingleton(this); - } - + @Override public void consumeUnconsumedPurchases() { - activity.runOnUiThread(new Runnable() { + runOnUiThread(new Runnable() { @Override public void run() { mPaymentManager.consumeUnconsumedPurchases(); @@ -82,74 +81,91 @@ public class GodotPaymentV3 extends Godot.SingletonBase { private String signature; + @Override public String getSignature() { return this.signature; } + @Override public void callbackSuccess(String ticket, String signature, String sku) { GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[] { ticket, signature, sku }); } + @Override public void callbackSuccessProductMassConsumed(String ticket, String signature, String sku) { Log.d(this.getClass().getName(), "callbackSuccessProductMassConsumed > " + ticket + "," + signature + "," + sku); GodotLib.calldeferred(purchaseCallbackId, "consume_success", new Object[] { ticket, signature, sku }); } + @Override public void callbackSuccessNoUnconsumedPurchases() { GodotLib.calldeferred(purchaseCallbackId, "consume_not_required", new Object[] {}); } + @Override public void callbackFailConsume(String message) { GodotLib.calldeferred(purchaseCallbackId, "consume_fail", new Object[] { message }); } + @Override public void callbackFail(String message) { GodotLib.calldeferred(purchaseCallbackId, "purchase_fail", new Object[] { message }); } + @Override public void callbackCancel() { GodotLib.calldeferred(purchaseCallbackId, "purchase_cancel", new Object[] {}); } + @Override public void callbackAlreadyOwned(String sku) { GodotLib.calldeferred(purchaseCallbackId, "purchase_owned", new Object[] { sku }); } + @Override public int getPurchaseCallbackId() { return purchaseCallbackId; } + @Override public void setPurchaseCallbackId(int purchaseCallbackId) { this.purchaseCallbackId = purchaseCallbackId; } + @Override public String getPurchaseValidationUrlPrefix() { return this.purchaseValidationUrlPrefix; } + @Override public void setPurchaseValidationUrlPrefix(String url) { this.purchaseValidationUrlPrefix = url; } + @Override public String getAccessToken() { return accessToken; } + @Override public void setAccessToken(String accessToken) { this.accessToken = accessToken; } + @Override public void setTransactionId(String transactionId) { this.transactionId = transactionId; } + @Override public String getTransactionId() { return this.transactionId; } // request purchased items are not consumed + @Override public void requestPurchased() { - activity.runOnUiThread(new Runnable() { + runOnUiThread(new Runnable() { @Override public void run() { mPaymentManager.requestPurchased(); @@ -158,34 +174,41 @@ public class GodotPaymentV3 extends Godot.SingletonBase { } // callback for requestPurchased() + @Override public void callbackPurchased(String receipt, String signature, String sku) { GodotLib.calldeferred(purchaseCallbackId, "has_purchased", new Object[] { receipt, signature, sku }); } + @Override public void callbackDisconnected() { GodotLib.calldeferred(purchaseCallbackId, "iap_disconnected", new Object[] {}); } + @Override public void callbackConnected() { GodotLib.calldeferred(purchaseCallbackId, "iap_connected", new Object[] {}); } // true if connected, false otherwise + @Override public boolean isConnected() { return mPaymentManager.isConnected(); } // consume item automatically after purchase. default is true. + @Override public void setAutoConsume(boolean autoConsume) { mPaymentManager.setAutoConsume(autoConsume); } // consume a specific item + @Override public void consume(String sku) { mPaymentManager.consume(sku); } // query in app item detail info + @Override public void querySkuDetails(String[] list) { List<String> nKeys = Arrays.asList(list); List<String> cKeys = Arrays.asList(mSkuDetails.get_keys()); @@ -202,6 +225,7 @@ public class GodotPaymentV3 extends Godot.SingletonBase { } } + @Override public void addSkuDetail(String itemJson) { JSONObject o = null; try { @@ -220,11 +244,25 @@ public class GodotPaymentV3 extends Godot.SingletonBase { } } + @Override public void completeSkuDetail() { GodotLib.calldeferred(purchaseCallbackId, "sku_details_complete", new Object[] { mSkuDetails }); } + @Override public void errorSkuDetail(String errorMessage) { GodotLib.calldeferred(purchaseCallbackId, "sku_details_error", new Object[] { errorMessage }); } + + @NonNull + @Override + public String getPluginName() { + return "GodotPayment"; + } + + @NonNull + @Override + public List<String> getPluginMethods() { + return Arrays.asList("purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", "setAutoConsume", "consume", "querySkuDetails", "isConnected"); + } } diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle index f6921c70aa..9536d3de6d 100644 --- a/platform/android/java/settings.gradle +++ b/platform/android/java/settings.gradle @@ -3,3 +3,4 @@ rootProject.name = "Godot" include ':app' include ':lib' +include ':plugins:godotpayment' diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 900a452024..0b1d070441 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -33,14 +33,15 @@ #include "java_godot_wrapper.h" #include "android/asset_manager_jni.h" +#include "android_keys_utils.h" #include "api/java_class_wrapper.h" #include "audio_driver_jandroid.h" #include "core/engine.h" -#include "core/os/keyboard.h" #include "core/project_settings.h" #include "dir_access_jandroid.h" #include "file_access_android.h" #include "file_access_jandroid.h" +#include "jni_utils.h" #include "main/input_default.h" #include "main/main.h" #include "net_socket_android.h" @@ -54,551 +55,6 @@ static OS_Android *os_android = NULL; static GodotJavaWrapper *godot_java = NULL; static GodotIOJavaWrapper *godot_io_java = NULL; -struct jvalret { - - jobject obj; - jvalue val; - jvalret() { obj = NULL; } -}; - -jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_arg, bool force_jobject = false) { - - jvalret v; - - switch (p_type) { - - case Variant::BOOL: { - - if (force_jobject) { - jclass bclass = env->FindClass("java/lang/Boolean"); - jmethodID ctor = env->GetMethodID(bclass, "<init>", "(Z)V"); - jvalue val; - val.z = (bool)(*p_arg); - jobject obj = env->NewObjectA(bclass, ctor, &val); - v.val.l = obj; - v.obj = obj; - env->DeleteLocalRef(bclass); - } else { - v.val.z = *p_arg; - }; - } break; - case Variant::INT: { - - if (force_jobject) { - - jclass bclass = env->FindClass("java/lang/Integer"); - jmethodID ctor = env->GetMethodID(bclass, "<init>", "(I)V"); - jvalue val; - val.i = (int)(*p_arg); - jobject obj = env->NewObjectA(bclass, ctor, &val); - v.val.l = obj; - v.obj = obj; - env->DeleteLocalRef(bclass); - - } else { - v.val.i = *p_arg; - }; - } break; - case Variant::FLOAT: { - - if (force_jobject) { - - jclass bclass = env->FindClass("java/lang/Double"); - jmethodID ctor = env->GetMethodID(bclass, "<init>", "(D)V"); - jvalue val; - val.d = (double)(*p_arg); - jobject obj = env->NewObjectA(bclass, ctor, &val); - v.val.l = obj; - v.obj = obj; - env->DeleteLocalRef(bclass); - - } else { - v.val.f = *p_arg; - }; - } break; - case Variant::STRING: { - - String s = *p_arg; - jstring jStr = env->NewStringUTF(s.utf8().get_data()); - v.val.l = jStr; - v.obj = jStr; - } break; - case Variant::PACKED_STRING_ARRAY: { - - Vector<String> sarray = *p_arg; - jobjectArray arr = env->NewObjectArray(sarray.size(), env->FindClass("java/lang/String"), env->NewStringUTF("")); - - for (int j = 0; j < sarray.size(); j++) { - - jstring str = env->NewStringUTF(sarray[j].utf8().get_data()); - env->SetObjectArrayElement(arr, j, str); - env->DeleteLocalRef(str); - } - v.val.l = arr; - v.obj = arr; - - } break; - - case Variant::DICTIONARY: { - - Dictionary dict = *p_arg; - jclass dclass = env->FindClass("org/godotengine/godot/Dictionary"); - jmethodID ctor = env->GetMethodID(dclass, "<init>", "()V"); - jobject jdict = env->NewObject(dclass, ctor); - - Array keys = dict.keys(); - - jobjectArray jkeys = env->NewObjectArray(keys.size(), env->FindClass("java/lang/String"), env->NewStringUTF("")); - for (int j = 0; j < keys.size(); j++) { - jstring str = env->NewStringUTF(String(keys[j]).utf8().get_data()); - env->SetObjectArrayElement(jkeys, j, str); - env->DeleteLocalRef(str); - }; - - jmethodID set_keys = env->GetMethodID(dclass, "set_keys", "([Ljava/lang/String;)V"); - jvalue val; - val.l = jkeys; - env->CallVoidMethodA(jdict, set_keys, &val); - env->DeleteLocalRef(jkeys); - - jobjectArray jvalues = env->NewObjectArray(keys.size(), env->FindClass("java/lang/Object"), NULL); - - for (int j = 0; j < keys.size(); j++) { - Variant var = dict[keys[j]]; - jvalret v = _variant_to_jvalue(env, var.get_type(), &var, true); - env->SetObjectArrayElement(jvalues, j, v.val.l); - if (v.obj) { - env->DeleteLocalRef(v.obj); - } - }; - - jmethodID set_values = env->GetMethodID(dclass, "set_values", "([Ljava/lang/Object;)V"); - val.l = jvalues; - env->CallVoidMethodA(jdict, set_values, &val); - env->DeleteLocalRef(jvalues); - env->DeleteLocalRef(dclass); - - v.val.l = jdict; - v.obj = jdict; - } break; - - case Variant::PACKED_INT32_ARRAY: { - - Vector<int> array = *p_arg; - jintArray arr = env->NewIntArray(array.size()); - const int *r = array.ptr(); - env->SetIntArrayRegion(arr, 0, array.size(), r.ptr()); - v.val.l = arr; - v.obj = arr; - - } break; - case Variant::PACKED_BYTE_ARRAY: { - Vector<uint8_t> array = *p_arg; - jbyteArray arr = env->NewByteArray(array.size()); - const uint8_t *r = array.ptr(); - env->SetByteArrayRegion(arr, 0, array.size(), reinterpret_cast<const signed char *>(r.ptr())); - v.val.l = arr; - v.obj = arr; - - } break; - case Variant::PACKED_FLOAT32_ARRAY: { - - Vector<float> array = *p_arg; - jfloatArray arr = env->NewFloatArray(array.size()); - const float *r = array.ptr(); - env->SetFloatArrayRegion(arr, 0, array.size(), r.ptr()); - v.val.l = arr; - v.obj = arr; - - } break; -#ifndef _MSC_VER -#warning This is missing 64 bits arrays, I have no idea how to do it in JNI -#endif - - default: { - - v.val.i = 0; - } break; - } - return v; -} - -String _get_class_name(JNIEnv *env, jclass cls, bool *array) { - - jclass cclass = env->FindClass("java/lang/Class"); - jmethodID getName = env->GetMethodID(cclass, "getName", "()Ljava/lang/String;"); - jstring clsName = (jstring)env->CallObjectMethod(cls, getName); - - if (array) { - jmethodID isArray = env->GetMethodID(cclass, "isArray", "()Z"); - jboolean isarr = env->CallBooleanMethod(cls, isArray); - (*array) = isarr ? true : false; - } - String name = jstring_to_string(clsName, env); - env->DeleteLocalRef(clsName); - - return name; -} - -Variant _jobject_to_variant(JNIEnv *env, jobject obj) { - - if (obj == NULL) { - return Variant(); - } - - jclass c = env->GetObjectClass(obj); - bool array; - String name = _get_class_name(env, c, &array); - - if (name == "java.lang.String") { - - return jstring_to_string((jstring)obj, env); - }; - - if (name == "[Ljava.lang.String;") { - - jobjectArray arr = (jobjectArray)obj; - int stringCount = env->GetArrayLength(arr); - Vector<String> sarr; - - for (int i = 0; i < stringCount; i++) { - jstring string = (jstring)env->GetObjectArrayElement(arr, i); - sarr.push_back(jstring_to_string(string, env)); - env->DeleteLocalRef(string); - } - - return sarr; - }; - - if (name == "java.lang.Boolean") { - - jmethodID boolValue = env->GetMethodID(c, "booleanValue", "()Z"); - bool ret = env->CallBooleanMethod(obj, boolValue); - return ret; - }; - - if (name == "java.lang.Integer" || name == "java.lang.Long") { - - jclass nclass = env->FindClass("java/lang/Number"); - jmethodID longValue = env->GetMethodID(nclass, "longValue", "()J"); - jlong ret = env->CallLongMethod(obj, longValue); - return ret; - }; - - if (name == "[I") { - - jintArray arr = (jintArray)obj; - int fCount = env->GetArrayLength(arr); - Vector<int> sarr; - sarr.resize(fCount); - - int *w = sarr.ptrw(); - env->GetIntArrayRegion(arr, 0, fCount, w.ptr()); - w.release(); - return sarr; - }; - - if (name == "[B") { - - jbyteArray arr = (jbyteArray)obj; - int fCount = env->GetArrayLength(arr); - Vector<uint8_t> sarr; - sarr.resize(fCount); - - uint8_t *w = sarr.ptrw(); - env->GetByteArrayRegion(arr, 0, fCount, reinterpret_cast<signed char *>(w.ptr())); - w.release(); - return sarr; - }; - - if (name == "java.lang.Float" || name == "java.lang.Double") { - - jclass nclass = env->FindClass("java/lang/Number"); - jmethodID doubleValue = env->GetMethodID(nclass, "doubleValue", "()D"); - double ret = env->CallDoubleMethod(obj, doubleValue); - return ret; - }; - - if (name == "[D") { - - jdoubleArray arr = (jdoubleArray)obj; - int fCount = env->GetArrayLength(arr); - PackedFloat32Array sarr; - sarr.resize(fCount); - - real_t *w = sarr.ptrw(); - - for (int i = 0; i < fCount; i++) { - - double n; - env->GetDoubleArrayRegion(arr, i, 1, &n); - w.ptr()[i] = n; - }; - return sarr; - }; - - if (name == "[F") { - - jfloatArray arr = (jfloatArray)obj; - int fCount = env->GetArrayLength(arr); - PackedFloat32Array sarr; - sarr.resize(fCount); - - real_t *w = sarr.ptrw(); - - for (int i = 0; i < fCount; i++) { - - float n; - env->GetFloatArrayRegion(arr, i, 1, &n); - w.ptr()[i] = n; - }; - return sarr; - }; - - if (name == "[Ljava.lang.Object;") { - - jobjectArray arr = (jobjectArray)obj; - int objCount = env->GetArrayLength(arr); - Array varr; - - for (int i = 0; i < objCount; i++) { - jobject jobj = env->GetObjectArrayElement(arr, i); - Variant v = _jobject_to_variant(env, jobj); - varr.push_back(v); - env->DeleteLocalRef(jobj); - } - - return varr; - }; - - if (name == "java.util.HashMap" || name == "org.godotengine.godot.Dictionary") { - - Dictionary ret; - jclass oclass = c; - jmethodID get_keys = env->GetMethodID(oclass, "get_keys", "()[Ljava/lang/String;"); - jobjectArray arr = (jobjectArray)env->CallObjectMethod(obj, get_keys); - - PackedStringArray keys = _jobject_to_variant(env, arr); - env->DeleteLocalRef(arr); - - jmethodID get_values = env->GetMethodID(oclass, "get_values", "()[Ljava/lang/Object;"); - arr = (jobjectArray)env->CallObjectMethod(obj, get_values); - - Array vals = _jobject_to_variant(env, arr); - env->DeleteLocalRef(arr); - - for (int i = 0; i < keys.size(); i++) { - - ret[keys[i]] = vals[i]; - }; - - return ret; - }; - - env->DeleteLocalRef(c); - - return Variant(); -} - -class JNISingleton : public Object { - - GDCLASS(JNISingleton, Object); - - struct MethodData { - - jmethodID method; - Variant::Type ret_type; - Vector<Variant::Type> argtypes; - }; - - jobject instance; - Map<StringName, MethodData> method_map; - -public: - virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - - ERR_FAIL_COND_V(!instance, Variant()); - - r_error.error = Callable::CallError::CALL_OK; - - Map<StringName, MethodData>::Element *E = method_map.find(p_method); - if (!E) { - - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - return Variant(); - } - - int ac = E->get().argtypes.size(); - if (ac < p_argcount) { - - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = ac; - return Variant(); - } - - if (ac > p_argcount) { - - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = ac; - return Variant(); - } - - for (int i = 0; i < p_argcount; i++) { - - if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) { - - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = i; - r_error.expected = E->get().argtypes[i]; - } - } - - jvalue *v = NULL; - - if (p_argcount) { - - v = (jvalue *)alloca(sizeof(jvalue) * p_argcount); - } - - JNIEnv *env = ThreadAndroid::get_env(); - - int res = env->PushLocalFrame(16); - - ERR_FAIL_COND_V(res != 0, Variant()); - - List<jobject> to_erase; - for (int i = 0; i < p_argcount; i++) { - - jvalret vr = _variant_to_jvalue(env, E->get().argtypes[i], p_args[i]); - v[i] = vr.val; - if (vr.obj) - to_erase.push_back(vr.obj); - } - - Variant ret; - - switch (E->get().ret_type) { - - case Variant::NIL: { - - env->CallVoidMethodA(instance, E->get().method, v); - } break; - case Variant::BOOL: { - - ret = env->CallBooleanMethodA(instance, E->get().method, v) == JNI_TRUE; - } break; - case Variant::INT: { - - ret = env->CallIntMethodA(instance, E->get().method, v); - } break; - case Variant::FLOAT: { - - ret = env->CallFloatMethodA(instance, E->get().method, v); - } break; - case Variant::STRING: { - - jobject o = env->CallObjectMethodA(instance, E->get().method, v); - ret = jstring_to_string((jstring)o, env); - env->DeleteLocalRef(o); - } break; - case Variant::PACKED_STRING_ARRAY: { - - jobjectArray arr = (jobjectArray)env->CallObjectMethodA(instance, E->get().method, v); - - ret = _jobject_to_variant(env, arr); - - env->DeleteLocalRef(arr); - } break; - case Variant::PACKED_INT32_ARRAY: { - - jintArray arr = (jintArray)env->CallObjectMethodA(instance, E->get().method, v); - - int fCount = env->GetArrayLength(arr); - Vector<int> sarr; - sarr.resize(fCount); - - int *w = sarr.ptrw(); - env->GetIntArrayRegion(arr, 0, fCount, w.ptr()); - w.release(); - ret = sarr; - env->DeleteLocalRef(arr); - } break; - case Variant::PACKED_FLOAT32_ARRAY: { - - jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance, E->get().method, v); - - int fCount = env->GetArrayLength(arr); - Vector<float> sarr; - sarr.resize(fCount); - - float *w = sarr.ptrw(); - env->GetFloatArrayRegion(arr, 0, fCount, w.ptr()); - w.release(); - ret = sarr; - env->DeleteLocalRef(arr); - } break; - -#ifndef _MSC_VER -#warning This is missing 64 bits arrays, I have no idea how to do it in JNI -#endif - case Variant::DICTIONARY: { - - jobject obj = env->CallObjectMethodA(instance, E->get().method, v); - ret = _jobject_to_variant(env, obj); - env->DeleteLocalRef(obj); - - } break; - default: { - - env->PopLocalFrame(NULL); - ERR_FAIL_V(Variant()); - } break; - } - - while (to_erase.size()) { - env->DeleteLocalRef(to_erase.front()->get()); - to_erase.pop_front(); - } - - env->PopLocalFrame(NULL); - - return ret; - } - - jobject get_instance() const { - - return instance; - } - void set_instance(jobject p_instance) { - - instance = p_instance; - } - - void add_method(const StringName &p_name, jmethodID p_method, const Vector<Variant::Type> &p_args, Variant::Type p_ret_type) { - - MethodData md; - md.method = p_method; - md.argtypes = p_args; - md.ret_type = p_ret_type; - method_map[p_name] = md; - } - - JNISingleton() { - instance = NULL; - } -}; - -struct TST { - - int a; - TST() { - - a = 5; - } -}; - -TST tst; - static bool initialized = false; static int step = 0; @@ -607,20 +63,16 @@ static Vector3 accelerometer; static Vector3 gravity; static Vector3 magnetometer; static Vector3 gyroscope; -static HashMap<String, JNISingleton *> jni_singletons; -// virtual Error native_video_play(String p_path); -// virtual bool native_video_is_playing(); -// virtual void native_video_pause(); -// virtual void native_video_stop(); +extern "C" { -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jobject obj, jint p_height) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height) { if (godot_io_java) { godot_io_java->set_vk_height(p_height); } } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion) { initialized = true; @@ -653,7 +105,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en godot_java->on_video_init(env); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jobject obj, jobject activity) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz, jobject activity) { // lets cleanup if (godot_io_java) { delete godot_io_java; @@ -666,47 +118,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env } } -static void _initialize_java_modules() { - - if (!ProjectSettings::get_singleton()->has_setting("android/modules")) { - return; - } - - String modules = ProjectSettings::get_singleton()->get("android/modules"); - modules = modules.strip_edges(); - if (modules == String()) { - return; - } - Vector<String> mods = modules.split(",", false); - - if (mods.size()) { - jobject cls = godot_java->get_class_loader(); - - // TODO create wrapper for class loader - - JNIEnv *env = ThreadAndroid::get_env(); - jclass classLoader = env->FindClass("java/lang/ClassLoader"); - jmethodID findClass = env->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); - - for (int i = 0; i < mods.size(); i++) { - - String m = mods[i]; - - print_line("Loading Android module: " + m); - jstring strClassName = env->NewStringUTF(m.utf8().get_data()); - jclass singletonClass = (jclass)env->CallObjectMethod(cls, findClass, strClassName); - ERR_CONTINUE_MSG(!singletonClass, "Couldn't find singleton for class: " + m + "."); - - jmethodID initialize = env->GetStaticMethodID(singletonClass, "initialize", "(Landroid/app/Activity;)Lorg/godotengine/godot/Godot$SingletonBase;"); - ERR_CONTINUE_MSG(!initialize, "Couldn't find proper initialize function 'public static Godot.SingletonBase Class::initialize(Activity p_activity)' initializer for singleton class: " + m + "."); - - jobject obj = env->CallStaticObjectMethod(singletonClass, initialize, godot_java->get_activity()); - env->NewGlobalRef(obj); - } - } -} - -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jobject obj, jobjectArray p_cmdline) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline) { ThreadAndroid::setup_thread(); const char **cmdline = NULL; @@ -746,16 +158,15 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jo } java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity())); - _initialize_java_modules(); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jobject obj, jint width, jint height) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jint width, jint height) { if (os_android) os_android->set_display_size(Size2(width, height)); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jobject obj, bool p_32_bits) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jboolean p_32_bits) { if (os_android) { if (step == 0) { @@ -770,14 +181,14 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *en } } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jobject obj) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz) { if (step == 0) return; os_android->main_loop_request_go_back(); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jobject obj) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) { if (step == -1) return; @@ -796,6 +207,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, job } os_android->main_loop_begin(); + godot_java->on_gl_godot_main_loop_started(env); ++step; } @@ -810,7 +222,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, job } } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jobject obj, jint ev, jint pointer, jint count, jintArray positions) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jclass clazz, jint ev, jint pointer, jint count, jintArray positions) { if (step == 0) return; @@ -834,282 +246,28 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jo */ } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jobject obj, jint p_type, jint p_x, jint p_y) { +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) return; os_android->process_hover(p_type, Point2(p_x, p_y)); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubletap(JNIEnv *env, jobject obj, 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) { if (step == 0) return; os_android->process_double_tap(Point2(p_x, p_y)); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jobject obj, 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) { if (step == 0) return; os_android->process_scroll(Point2(p_x, p_y)); } -/* - * 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, - - // 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; -}; - -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_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, -*/ - -static unsigned int 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; -} - -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jobject obj, jint p_device, jint p_button, 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) { if (step == 0) return; @@ -1122,7 +280,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env os_android->process_joy_event(jevent); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jobject obj, jint p_device, jint p_axis, jfloat p_value) { +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) return; @@ -1135,7 +293,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, os_android->process_joy_event(jevent); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jobject obj, jint p_device, jint p_hat_x, jint p_hat_y) { +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) return; @@ -1160,58 +318,59 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, j os_android->process_joy_event(jevent); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jobject obj, jint p_device, jboolean p_connected, jstring p_name) { +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); os_android->joy_connection_changed(p_device, p_connected, name); } } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jobject obj, jint p_scancode, jint p_unicode_char, jboolean p_pressed) { +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) return; Ref<InputEventKey> ievent; ievent.instance(); int val = p_unicode_char; - int scancode = android_get_keysym(p_scancode); - ievent->set_scancode(scancode); + int keycode = android_get_keysym(p_keycode); + int phy_keycode = android_get_keysym(p_scancode); + ievent->set_keycode(keycode); + ievent->set_physical_keycode(phy_keycode); ievent->set_unicode(val); ievent->set_pressed(p_pressed); if (val == '\n') { - ievent->set_scancode(KEY_ENTER); + ievent->set_keycode(KEY_ENTER); } else if (val == 61448) { - ievent->set_scancode(KEY_BACKSPACE); + ievent->set_keycode(KEY_BACKSPACE); ievent->set_unicode(KEY_BACKSPACE); } else if (val == 61453) { - ievent->set_scancode(KEY_ENTER); + ievent->set_keycode(KEY_ENTER); ievent->set_unicode(KEY_ENTER); - } else if (p_scancode == 4) { - + } else if (p_keycode == 4) { os_android->main_loop_request_go_back(); } os_android->process_event(ievent); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) { accelerometer = Vector3(x, y, z); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gravity(JNIEnv *env, jobject obj, 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) { gravity = Vector3(x, y, z); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *env, jobject obj, 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) { magnetometer = Vector3(x, y, z); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) { gyroscope = Vector3(x, y, z); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jobject obj) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz) { if (step == 0) return; @@ -1219,7 +378,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, os_android->main_loop_focusin(); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jobject obj) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz) { if (step == 0) return; @@ -1227,134 +386,22 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, os_android->main_loop_focusout(); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jobject obj) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jclass clazz) { ThreadAndroid::setup_thread(); AudioDriverAndroid::thread_func(env); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_singleton(JNIEnv *env, jobject obj, jstring name, jobject p_object) { - - String singname = jstring_to_string(name, env); - JNISingleton *s = memnew(JNISingleton); - s->set_instance(env->NewGlobalRef(p_object)); - jni_singletons[singname] = s; - - Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s)); - ProjectSettings::get_singleton()->set(singname, s); -} - -static Variant::Type get_jni_type(const String &p_type) { - - static struct { - const char *name; - Variant::Type type; - } _type_to_vtype[] = { - { "void", Variant::NIL }, - { "boolean", Variant::BOOL }, - { "int", Variant::INT }, - { "float", Variant::FLOAT }, - { "double", Variant::FLOAT }, - { "java.lang.String", Variant::STRING }, - { "[I", Variant::PACKED_INT32_ARRAY }, - { "[B", Variant::PACKED_BYTE_ARRAY }, - { "[F", Variant::PACKED_FLOAT32_ARRAY }, - { "[Ljava.lang.String;", Variant::PACKED_STRING_ARRAY }, - { "org.godotengine.godot.Dictionary", Variant::DICTIONARY }, - { NULL, Variant::NIL } - }; - - int idx = 0; - - while (_type_to_vtype[idx].name) { - - if (p_type == _type_to_vtype[idx].name) - return _type_to_vtype[idx].type; - - idx++; - } - - return Variant::NIL; -} - -static const char *get_jni_sig(const String &p_type) { - - static struct { - const char *name; - const char *sig; - } _type_to_vtype[] = { - { "void", "V" }, - { "boolean", "Z" }, - { "int", "I" }, - { "float", "F" }, - { "double", "D" }, - { "java.lang.String", "Ljava/lang/String;" }, - { "org.godotengine.godot.Dictionary", "Lorg/godotengine/godot/Dictionary;" }, - { "[I", "[I" }, - { "[B", "[B" }, - { "[F", "[F" }, - { "[Ljava.lang.String;", "[Ljava/lang/String;" }, - { NULL, "V" } - }; - - int idx = 0; - - while (_type_to_vtype[idx].name) { - - if (p_type == _type_to_vtype[idx].name) - return _type_to_vtype[idx].sig; - - idx++; - } - - return "Ljava/lang/Object;"; -} - -JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jobject obj, jstring path) { +JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path) { String js = jstring_to_string(path, env); return env->NewStringUTF(ProjectSettings::get_singleton()->get(js).operator String().utf8().get_data()); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_method(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args) { - - String singname = jstring_to_string(sname, env); - - ERR_FAIL_COND(!jni_singletons.has(singname)); - - JNISingleton *s = jni_singletons.get(singname); - - String mname = jstring_to_string(name, env); - String retval = jstring_to_string(ret, env); - Vector<Variant::Type> types; - String cs = "("; - - int stringCount = env->GetArrayLength(args); - - for (int i = 0; i < stringCount; i++) { - - jstring string = (jstring)env->GetObjectArrayElement(args, i); - const String rawString = jstring_to_string(string, env); - types.push_back(get_jni_type(rawString)); - cs += get_jni_sig(rawString); - } - - cs += ")"; - cs += get_jni_sig(retval); - jclass cls = env->GetObjectClass(s->get_instance()); - jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data()); - if (!mid) { - - print_line("Failed getting method ID " + mname); - } - - s->add_method(mname, mid, types, get_jni_type(retval)); -} - -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jint ID, jstring method, jobjectArray params) { - Object *obj = ObjectDB::get_instance(ID); + Object *obj = ObjectDB::get_instance(ObjectID((uint64_t)ID)); ERR_FAIL_COND(!obj); int res = env->PushLocalFrame(16); @@ -1384,9 +431,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *en env->PopLocalFrame(NULL); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jint ID, jstring method, jobjectArray params) { - Object *obj = ObjectDB::get_instance(ID); + Object *obj = ObjectDB::get_instance(ObjectID((uint64_t)ID)); ERR_FAIL_COND(!obj); int res = env->PushLocalFrame(16); @@ -1410,7 +457,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv * env->PopLocalFrame(NULL); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jobject p_obj, jstring p_permission, jboolean p_result) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result) { String permission = jstring_to_string(p_permission, env); if (permission == "android.permission.RECORD_AUDIO" && p_result) { AudioDriver::get_singleton()->capture_start(); @@ -1438,3 +485,4 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIE os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APP_PAUSED); } } +} diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index 71d4547f65..a7a5970440 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -37,36 +37,34 @@ // These functions can be called from within JAVA and are the means by which our JAVA implementation calls back into our C++ code. // See java/src/org/godotengine/godot/GodotLib.java for the JAVA side of this (yes that's why we have the long names) extern "C" { -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jobject obj, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jobject obj, jobject activity); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jobject obj, jobjectArray p_cmdline); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jobject obj, jint width, jint height); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jobject obj, bool p_32_bits); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jobject obj); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jobject obj); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jobject obj, jint ev, jint pointer, jint count, jintArray positions); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jobject obj, jint p_type, jint p_x, jint p_y); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubletap(JNIEnv *env, jobject obj, jint p_x, jint p_y); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jobject obj, jint p_x, jint p_y); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jobject obj, jint p_scancode, jint p_unicode_char, jboolean p_pressed); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jobject obj, jint p_device, jint p_button, jboolean p_pressed); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jobject obj, jint p_device, jint p_axis, jfloat p_value); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jobject obj, jint p_device, jint p_hat_x, jint p_hat_y); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jobject obj, jint p_device, jboolean p_connected, jstring p_name); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jobject obj); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gravity(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jobject obj); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jobject obj); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_singleton(JNIEnv *env, jobject obj, jstring name, jobject p_object); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_method(JNIEnv *env, jobject obj, jstring sname, jstring name, jstring ret, jobjectArray args); -JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jobject obj, jstring path); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jobject obj, jint p_height); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jobject p_obj, jstring p_permission, jboolean p_result); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz, jobject activity); +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, jint width, jint height); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jboolean p_32_bits); +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); +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); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz); +JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jint ID, jstring method, jobjectArray params); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jint ID, jstring method, jobjectArray params); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz); } diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 9ac91b8ef6..7b677c186e 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -66,6 +66,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) { _is_activity_resumed = p_env->GetMethodID(cls, "isActivityResumed", "()Z"); _vibrate = p_env->GetMethodID(cls, "vibrate", "(I)V"); _get_input_fallback_mapping = p_env->GetMethodID(cls, "getInputFallbackMapping", "()Ljava/lang/String;"); + _on_gl_godot_main_loop_started = p_env->GetMethodID(cls, "onGLGodotMainLoopStarted", "()V"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -107,6 +108,15 @@ void GodotJavaWrapper::on_video_init(JNIEnv *p_env) { p_env->CallVoidMethod(godot_instance, _on_video_init); } +void GodotJavaWrapper::on_gl_godot_main_loop_started(JNIEnv *p_env) { + if (_on_gl_godot_main_loop_started) { + if (p_env == NULL) { + p_env = ThreadAndroid::get_env(); + } + } + p_env->CallVoidMethod(godot_instance, _on_gl_godot_main_loop_started); +} + void GodotJavaWrapper::restart(JNIEnv *p_env) { if (_restart) if (p_env == NULL) diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index f378b1ea38..cdab2ecc9c 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -61,6 +61,7 @@ private: jmethodID _is_activity_resumed = 0; jmethodID _vibrate = 0; jmethodID _get_input_fallback_mapping = 0; + jmethodID _on_gl_godot_main_loop_started = 0; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance); @@ -72,6 +73,7 @@ public: jobject get_class_loader(); void on_video_init(JNIEnv *p_env = NULL); + void on_gl_godot_main_loop_started(JNIEnv *p_env = NULL); void restart(JNIEnv *p_env = NULL); void force_quit(JNIEnv *p_env = NULL); void set_keep_screen_on(bool p_enabled); diff --git a/platform/android/jni_utils.cpp b/platform/android/jni_utils.cpp new file mode 100644 index 0000000000..3fa4e80884 --- /dev/null +++ b/platform/android/jni_utils.cpp @@ -0,0 +1,434 @@ +/*************************************************************************/ +/* jni_utils.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 "jni_utils.h" + +jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_arg, bool force_jobject) { + + jvalret v; + + switch (p_type) { + + case Variant::BOOL: { + + if (force_jobject) { + jclass bclass = env->FindClass("java/lang/Boolean"); + jmethodID ctor = env->GetMethodID(bclass, "<init>", "(Z)V"); + jvalue val; + val.z = (bool)(*p_arg); + jobject obj = env->NewObjectA(bclass, ctor, &val); + v.val.l = obj; + v.obj = obj; + env->DeleteLocalRef(bclass); + } else { + v.val.z = *p_arg; + }; + } break; + case Variant::INT: { + + if (force_jobject) { + + jclass bclass = env->FindClass("java/lang/Integer"); + jmethodID ctor = env->GetMethodID(bclass, "<init>", "(I)V"); + jvalue val; + val.i = (int)(*p_arg); + jobject obj = env->NewObjectA(bclass, ctor, &val); + v.val.l = obj; + v.obj = obj; + env->DeleteLocalRef(bclass); + + } else { + v.val.i = *p_arg; + }; + } break; + case Variant::FLOAT: { + + if (force_jobject) { + + jclass bclass = env->FindClass("java/lang/Double"); + jmethodID ctor = env->GetMethodID(bclass, "<init>", "(D)V"); + jvalue val; + val.d = (double)(*p_arg); + jobject obj = env->NewObjectA(bclass, ctor, &val); + v.val.l = obj; + v.obj = obj; + env->DeleteLocalRef(bclass); + + } else { + v.val.f = *p_arg; + }; + } break; + case Variant::STRING: { + + String s = *p_arg; + jstring jStr = env->NewStringUTF(s.utf8().get_data()); + v.val.l = jStr; + v.obj = jStr; + } break; + case Variant::PACKED_STRING_ARRAY: { + + Vector<String> sarray = *p_arg; + jobjectArray arr = env->NewObjectArray(sarray.size(), env->FindClass("java/lang/String"), env->NewStringUTF("")); + + for (int j = 0; j < sarray.size(); j++) { + + jstring str = env->NewStringUTF(sarray[j].utf8().get_data()); + env->SetObjectArrayElement(arr, j, str); + env->DeleteLocalRef(str); + } + v.val.l = arr; + v.obj = arr; + + } break; + + case Variant::DICTIONARY: { + + Dictionary dict = *p_arg; + jclass dclass = env->FindClass("org/godotengine/godot/Dictionary"); + jmethodID ctor = env->GetMethodID(dclass, "<init>", "()V"); + jobject jdict = env->NewObject(dclass, ctor); + + Array keys = dict.keys(); + + jobjectArray jkeys = env->NewObjectArray(keys.size(), env->FindClass("java/lang/String"), env->NewStringUTF("")); + for (int j = 0; j < keys.size(); j++) { + jstring str = env->NewStringUTF(String(keys[j]).utf8().get_data()); + env->SetObjectArrayElement(jkeys, j, str); + env->DeleteLocalRef(str); + }; + + jmethodID set_keys = env->GetMethodID(dclass, "set_keys", "([Ljava/lang/String;)V"); + jvalue val; + val.l = jkeys; + env->CallVoidMethodA(jdict, set_keys, &val); + env->DeleteLocalRef(jkeys); + + jobjectArray jvalues = env->NewObjectArray(keys.size(), env->FindClass("java/lang/Object"), NULL); + + for (int j = 0; j < keys.size(); j++) { + Variant var = dict[keys[j]]; + jvalret v = _variant_to_jvalue(env, var.get_type(), &var, true); + env->SetObjectArrayElement(jvalues, j, v.val.l); + if (v.obj) { + env->DeleteLocalRef(v.obj); + } + }; + + jmethodID set_values = env->GetMethodID(dclass, "set_values", "([Ljava/lang/Object;)V"); + val.l = jvalues; + env->CallVoidMethodA(jdict, set_values, &val); + env->DeleteLocalRef(jvalues); + env->DeleteLocalRef(dclass); + + v.val.l = jdict; + v.obj = jdict; + } break; + + case Variant::PACKED_INT32_ARRAY: { + + Vector<int> array = *p_arg; + jintArray arr = env->NewIntArray(array.size()); + const int *r = array.ptr(); + env->SetIntArrayRegion(arr, 0, array.size(), r); + v.val.l = arr; + v.obj = arr; + + } break; + case Variant::PACKED_BYTE_ARRAY: { + Vector<uint8_t> array = *p_arg; + jbyteArray arr = env->NewByteArray(array.size()); + const uint8_t *r = array.ptr(); + env->SetByteArrayRegion(arr, 0, array.size(), reinterpret_cast<const signed char *>(r)); + v.val.l = arr; + v.obj = arr; + + } break; + case Variant::PACKED_FLOAT32_ARRAY: { + + Vector<float> array = *p_arg; + jfloatArray arr = env->NewFloatArray(array.size()); + const float *r = array.ptr(); + env->SetFloatArrayRegion(arr, 0, array.size(), r); + v.val.l = arr; + v.obj = arr; + + } break; +#ifndef _MSC_VER +#warning This is missing 64 bits arrays, I have no idea how to do it in JNI +#endif + + default: { + + v.val.i = 0; + } break; + } + return v; +} + +String _get_class_name(JNIEnv *env, jclass cls, bool *array) { + + jclass cclass = env->FindClass("java/lang/Class"); + jmethodID getName = env->GetMethodID(cclass, "getName", "()Ljava/lang/String;"); + jstring clsName = (jstring)env->CallObjectMethod(cls, getName); + + if (array) { + jmethodID isArray = env->GetMethodID(cclass, "isArray", "()Z"); + jboolean isarr = env->CallBooleanMethod(cls, isArray); + (*array) = isarr ? true : false; + } + String name = jstring_to_string(clsName, env); + env->DeleteLocalRef(clsName); + + return name; +} + +Variant _jobject_to_variant(JNIEnv *env, jobject obj) { + + if (obj == NULL) { + return Variant(); + } + + jclass c = env->GetObjectClass(obj); + bool array; + String name = _get_class_name(env, c, &array); + + if (name == "java.lang.String") { + + return jstring_to_string((jstring)obj, env); + }; + + if (name == "[Ljava.lang.String;") { + + jobjectArray arr = (jobjectArray)obj; + int stringCount = env->GetArrayLength(arr); + Vector<String> sarr; + + for (int i = 0; i < stringCount; i++) { + jstring string = (jstring)env->GetObjectArrayElement(arr, i); + sarr.push_back(jstring_to_string(string, env)); + env->DeleteLocalRef(string); + } + + return sarr; + }; + + if (name == "java.lang.Boolean") { + + jmethodID boolValue = env->GetMethodID(c, "booleanValue", "()Z"); + bool ret = env->CallBooleanMethod(obj, boolValue); + return ret; + }; + + if (name == "java.lang.Integer" || name == "java.lang.Long") { + + jclass nclass = env->FindClass("java/lang/Number"); + jmethodID longValue = env->GetMethodID(nclass, "longValue", "()J"); + jlong ret = env->CallLongMethod(obj, longValue); + return ret; + }; + + if (name == "[I") { + + jintArray arr = (jintArray)obj; + int fCount = env->GetArrayLength(arr); + Vector<int> sarr; + sarr.resize(fCount); + + int *w = sarr.ptrw(); + env->GetIntArrayRegion(arr, 0, fCount, w); + return sarr; + }; + + if (name == "[B") { + + jbyteArray arr = (jbyteArray)obj; + int fCount = env->GetArrayLength(arr); + Vector<uint8_t> sarr; + sarr.resize(fCount); + + uint8_t *w = sarr.ptrw(); + env->GetByteArrayRegion(arr, 0, fCount, reinterpret_cast<signed char *>(w)); + return sarr; + }; + + if (name == "java.lang.Float" || name == "java.lang.Double") { + + jclass nclass = env->FindClass("java/lang/Number"); + jmethodID doubleValue = env->GetMethodID(nclass, "doubleValue", "()D"); + double ret = env->CallDoubleMethod(obj, doubleValue); + return ret; + }; + + if (name == "[D") { + + jdoubleArray arr = (jdoubleArray)obj; + int fCount = env->GetArrayLength(arr); + PackedFloat32Array sarr; + sarr.resize(fCount); + + real_t *w = sarr.ptrw(); + + for (int i = 0; i < fCount; i++) { + + double n; + env->GetDoubleArrayRegion(arr, i, 1, &n); + w[i] = n; + }; + return sarr; + }; + + if (name == "[F") { + + jfloatArray arr = (jfloatArray)obj; + int fCount = env->GetArrayLength(arr); + PackedFloat32Array sarr; + sarr.resize(fCount); + + real_t *w = sarr.ptrw(); + + for (int i = 0; i < fCount; i++) { + + float n; + env->GetFloatArrayRegion(arr, i, 1, &n); + w[i] = n; + }; + return sarr; + }; + + if (name == "[Ljava.lang.Object;") { + + jobjectArray arr = (jobjectArray)obj; + int objCount = env->GetArrayLength(arr); + Array varr; + + for (int i = 0; i < objCount; i++) { + jobject jobj = env->GetObjectArrayElement(arr, i); + Variant v = _jobject_to_variant(env, jobj); + varr.push_back(v); + env->DeleteLocalRef(jobj); + } + + return varr; + }; + + if (name == "java.util.HashMap" || name == "org.godotengine.godot.Dictionary") { + + Dictionary ret; + jclass oclass = c; + jmethodID get_keys = env->GetMethodID(oclass, "get_keys", "()[Ljava/lang/String;"); + jobjectArray arr = (jobjectArray)env->CallObjectMethod(obj, get_keys); + + PackedStringArray keys = _jobject_to_variant(env, arr); + env->DeleteLocalRef(arr); + + jmethodID get_values = env->GetMethodID(oclass, "get_values", "()[Ljava/lang/Object;"); + arr = (jobjectArray)env->CallObjectMethod(obj, get_values); + + Array vals = _jobject_to_variant(env, arr); + env->DeleteLocalRef(arr); + + for (int i = 0; i < keys.size(); i++) { + + ret[keys[i]] = vals[i]; + }; + + return ret; + }; + + env->DeleteLocalRef(c); + + return Variant(); +} + +Variant::Type get_jni_type(const String &p_type) { + + static struct { + const char *name; + Variant::Type type; + } _type_to_vtype[] = { + { "void", Variant::NIL }, + { "boolean", Variant::BOOL }, + { "int", Variant::INT }, + { "float", Variant::FLOAT }, + { "double", Variant::FLOAT }, + { "java.lang.String", Variant::STRING }, + { "[I", Variant::PACKED_INT32_ARRAY }, + { "[B", Variant::PACKED_BYTE_ARRAY }, + { "[F", Variant::PACKED_FLOAT32_ARRAY }, + { "[Ljava.lang.String;", Variant::PACKED_STRING_ARRAY }, + { "org.godotengine.godot.Dictionary", Variant::DICTIONARY }, + { NULL, Variant::NIL } + }; + + int idx = 0; + + while (_type_to_vtype[idx].name) { + + if (p_type == _type_to_vtype[idx].name) + return _type_to_vtype[idx].type; + + idx++; + } + + return Variant::NIL; +} + +const char *get_jni_sig(const String &p_type) { + + static struct { + const char *name; + const char *sig; + } _type_to_vtype[] = { + { "void", "V" }, + { "boolean", "Z" }, + { "int", "I" }, + { "float", "F" }, + { "double", "D" }, + { "java.lang.String", "Ljava/lang/String;" }, + { "org.godotengine.godot.Dictionary", "Lorg/godotengine/godot/Dictionary;" }, + { "[I", "[I" }, + { "[B", "[B" }, + { "[F", "[F" }, + { "[Ljava.lang.String;", "[Ljava/lang/String;" }, + { NULL, "V" } + }; + + int idx = 0; + + while (_type_to_vtype[idx].name) { + + if (p_type == _type_to_vtype[idx].name) + return _type_to_vtype[idx].sig; + + idx++; + } + + return "Ljava/lang/Object;"; +} diff --git a/platform/android/jni_utils.h b/platform/android/jni_utils.h new file mode 100644 index 0000000000..925340a680 --- /dev/null +++ b/platform/android/jni_utils.h @@ -0,0 +1,242 @@ +/*************************************************************************/ +/* jni_utils.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 JNI_UTILS_H +#define JNI_UTILS_H + +#include "string_android.h" +#include <core/engine.h> +#include <core/variant.h> +#include <jni.h> + +struct jvalret { + + jobject obj; + jvalue val; + jvalret() { obj = NULL; } +}; + +jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_arg, bool force_jobject = false); + +String _get_class_name(JNIEnv *env, jclass cls, bool *array); + +Variant _jobject_to_variant(JNIEnv *env, jobject obj); + +Variant::Type get_jni_type(const String &p_type); + +const char *get_jni_sig(const String &p_type); + +class JNISingleton : public Object { + + GDCLASS(JNISingleton, Object); + + struct MethodData { + + jmethodID method; + Variant::Type ret_type; + Vector<Variant::Type> argtypes; + }; + + jobject instance; + Map<StringName, MethodData> method_map; + +public: + virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + + ERR_FAIL_COND_V(!instance, Variant()); + + r_error.error = Callable::CallError::CALL_OK; + + Map<StringName, MethodData>::Element *E = method_map.find(p_method); + if (!E) { + + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + return Variant(); + } + + int ac = E->get().argtypes.size(); + if (ac < p_argcount) { + + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = ac; + return Variant(); + } + + if (ac > p_argcount) { + + r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.argument = ac; + return Variant(); + } + + for (int i = 0; i < p_argcount; i++) { + + if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) { + + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = i; + r_error.expected = E->get().argtypes[i]; + } + } + + jvalue *v = NULL; + + if (p_argcount) { + + v = (jvalue *)alloca(sizeof(jvalue) * p_argcount); + } + + JNIEnv *env = ThreadAndroid::get_env(); + + int res = env->PushLocalFrame(16); + + ERR_FAIL_COND_V(res != 0, Variant()); + + List<jobject> to_erase; + for (int i = 0; i < p_argcount; i++) { + + jvalret vr = _variant_to_jvalue(env, E->get().argtypes[i], p_args[i]); + v[i] = vr.val; + if (vr.obj) + to_erase.push_back(vr.obj); + } + + Variant ret; + + switch (E->get().ret_type) { + + case Variant::NIL: { + + env->CallVoidMethodA(instance, E->get().method, v); + } break; + case Variant::BOOL: { + + ret = env->CallBooleanMethodA(instance, E->get().method, v) == JNI_TRUE; + } break; + case Variant::INT: { + + ret = env->CallIntMethodA(instance, E->get().method, v); + } break; + case Variant::FLOAT: { + + ret = env->CallFloatMethodA(instance, E->get().method, v); + } break; + case Variant::STRING: { + + jobject o = env->CallObjectMethodA(instance, E->get().method, v); + ret = jstring_to_string((jstring)o, env); + env->DeleteLocalRef(o); + } break; + case Variant::PACKED_STRING_ARRAY: { + + jobjectArray arr = (jobjectArray)env->CallObjectMethodA(instance, E->get().method, v); + + ret = _jobject_to_variant(env, arr); + + env->DeleteLocalRef(arr); + } break; + case Variant::PACKED_INT32_ARRAY: { + + jintArray arr = (jintArray)env->CallObjectMethodA(instance, E->get().method, v); + + int fCount = env->GetArrayLength(arr); + Vector<int> sarr; + sarr.resize(fCount); + + int *w = sarr.ptrw(); + env->GetIntArrayRegion(arr, 0, fCount, w); + ret = sarr; + env->DeleteLocalRef(arr); + } break; + case Variant::PACKED_FLOAT32_ARRAY: { + + jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance, E->get().method, v); + + int fCount = env->GetArrayLength(arr); + Vector<float> sarr; + sarr.resize(fCount); + + float *w = sarr.ptrw(); + env->GetFloatArrayRegion(arr, 0, fCount, w); + ret = sarr; + env->DeleteLocalRef(arr); + } break; + +#ifndef _MSC_VER +#warning This is missing 64 bits arrays, I have no idea how to do it in JNI +#endif + case Variant::DICTIONARY: { + + jobject obj = env->CallObjectMethodA(instance, E->get().method, v); + ret = _jobject_to_variant(env, obj); + env->DeleteLocalRef(obj); + + } break; + default: { + + env->PopLocalFrame(NULL); + ERR_FAIL_V(Variant()); + } break; + } + + while (to_erase.size()) { + env->DeleteLocalRef(to_erase.front()->get()); + to_erase.pop_front(); + } + + env->PopLocalFrame(NULL); + + return ret; + } + + jobject get_instance() const { + + return instance; + } + void set_instance(jobject p_instance) { + + instance = p_instance; + } + + void add_method(const StringName &p_name, jmethodID p_method, const Vector<Variant::Type> &p_args, Variant::Type p_ret_type) { + + MethodData md; + md.method = p_method; + md.argtypes = p_args; + md.ret_type = p_ret_type; + method_map[p_name] = md; + } + + JNISingleton() { + instance = NULL; + } +}; + +#endif // JNI_UTILS_H diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 15e3ac48c7..7e2b0d948e 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -32,7 +32,9 @@ #include "core/io/file_access_buffered_fa.h" #include "core/project_settings.h" +#if defined(OPENGL_ENABLED) #include "drivers/gles2/rasterizer_gles2.h" +#endif #include "drivers/unix/dir_access_unix.h" #include "drivers/unix/file_access_unix.h" #include "file_access_android.h" @@ -120,23 +122,27 @@ int OS_Android::get_current_video_driver() const { Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - bool gl_initialization_error = false; - // FIXME: Add Vulkan support. Readd fallback code from Vulkan to GLES2? - if (RasterizerGLES2::is_viable() == OK) { - RasterizerGLES2::register_config(); - RasterizerGLES2::make_current(); - } else { - gl_initialization_error = true; - } - - if (gl_initialization_error) { - OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.\n" - "Please try updating your Android version.", - "Unable to initialize video driver"); - return ERR_UNAVAILABLE; +#if defined(OPENGL_ENABLED) + if (video_driver_index == VIDEO_DRIVER_GLES2) { + bool gl_initialization_error = false; + + if (RasterizerGLES2::is_viable() == OK) { + RasterizerGLES2::register_config(); + RasterizerGLES2::make_current(); + } else { + gl_initialization_error = true; + } + + if (gl_initialization_error) { + OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.\n" + "Please try updating your Android version.", + "Unable to initialize video driver"); + return ERR_UNAVAILABLE; + } } +#endif video_driver_index = p_video_driver; @@ -753,6 +759,8 @@ OS_Android::OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_god //rasterizer = NULL; use_gl2 = false; + visual_server = NULL; + godot_java = p_godot_java; godot_io_java = p_godot_io_java; diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp new file mode 100644 index 0000000000..7413236e5d --- /dev/null +++ b/platform/android/plugin/godot_plugin_jni.cpp @@ -0,0 +1,115 @@ +/*************************************************************************/ +/* godot_plugin_jni.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 "godot_plugin_jni.h" + +#include <core/engine.h> +#include <core/error_macros.h> +#include <core/project_settings.h> +#include <platform/android/jni_utils.h> +#include <platform/android/string_android.h> + +static HashMap<String, JNISingleton *> jni_singletons; + +extern "C" { + +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jobject obj, jstring name) { + + String singname = jstring_to_string(name, env); + JNISingleton *s = memnew(JNISingleton); + s->set_instance(env->NewGlobalRef(obj)); + jni_singletons[singname] = s; + + Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s)); + 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) { + + String singname = jstring_to_string(sname, env); + + ERR_FAIL_COND(!jni_singletons.has(singname)); + + JNISingleton *s = jni_singletons.get(singname); + + String mname = jstring_to_string(name, env); + String retval = jstring_to_string(ret, env); + Vector<Variant::Type> types; + String cs = "("; + + int stringCount = env->GetArrayLength(args); + + for (int i = 0; i < stringCount; i++) { + + jstring string = (jstring)env->GetObjectArrayElement(args, i); + const String rawString = jstring_to_string(string, env); + types.push_back(get_jni_type(rawString)); + cs += get_jni_sig(rawString); + } + + cs += ")"; + cs += get_jni_sig(retval); + jclass cls = env->GetObjectClass(s->get_instance()); + jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data()); + if (!mid) { + + print_line("Failed getting method ID " + mname); + } + + s->add_method(mname, mid, types, get_jni_type(retval)); +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jobject obj, jobjectArray gdnlib_paths) { + int gdnlib_count = env->GetArrayLength(gdnlib_paths); + if (gdnlib_count == 0) { + return; + } + + // Retrieve the current list of gdnative libraries. + Array singletons = Array(); + if (ProjectSettings::get_singleton()->has_setting("gdnative/singletons")) { + singletons = ProjectSettings::get_singleton()->get("gdnative/singletons"); + } + + // Insert the libraries provided by the plugin + for (int i = 0; i < gdnlib_count; i++) { + jstring relative_path = (jstring)env->GetObjectArrayElement(gdnlib_paths, i); + + String path = "res://" + jstring_to_string(relative_path, env); + if (!singletons.has(path)) { + singletons.push_back(path); + } + env->DeleteLocalRef(relative_path); + } + + // Insert the updated list back into project settings. + ProjectSettings::get_singleton()->set("gdnative/singletons", singletons); +} +} diff --git a/platform/android/plugin/godot_plugin_jni.h b/platform/android/plugin/godot_plugin_jni.h new file mode 100644 index 0000000000..0d613d3bfe --- /dev/null +++ b/platform/android/plugin/godot_plugin_jni.h @@ -0,0 +1,43 @@ +/*************************************************************************/ +/* godot_plugin_jni.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_JNI_H +#define GODOT_PLUGIN_JNI_H + +#include <android/log.h> +#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_nativeRegisterGDNativeLibraries(JNIEnv *env, jobject obj, jobjectArray gdnlib_paths); +} + +#endif // GODOT_PLUGIN_JNI_H diff --git a/platform/android/vulkan/vk_renderer_jni.cpp b/platform/android/vulkan/vk_renderer_jni.cpp new file mode 100644 index 0000000000..3026e7daad --- /dev/null +++ b/platform/android/vulkan/vk_renderer_jni.cpp @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* vk_renderer_jni.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 "vk_renderer_jni.h" + +extern "C" { + +JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkSurfaceCreated(JNIEnv *env, jobject obj, jobject j_surface) { + // TODO: complete +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkSurfaceChanged(JNIEnv *env, jobject object, jobject j_surface, jint width, jint height) { + // TODO: complete +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkResume(JNIEnv *env, jobject obj) { + // TODO: complete +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkDrawFrame(JNIEnv *env, jobject obj) { + // TODO: complete +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkPause(JNIEnv *env, jobject obj) { + // TODO: complete +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkDestroy(JNIEnv *env, jobject obj) { + // TODO: complete +} +} diff --git a/platform/android/vulkan/vk_renderer_jni.h b/platform/android/vulkan/vk_renderer_jni.h new file mode 100644 index 0000000000..017766fea2 --- /dev/null +++ b/platform/android/vulkan/vk_renderer_jni.h @@ -0,0 +1,46 @@ +/*************************************************************************/ +/* vk_renderer_jni.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 VK_RENDERER_JNI_H +#define VK_RENDERER_JNI_H + +#include <android/log.h> +#include <jni.h> + +extern "C" { +JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkSurfaceCreated(JNIEnv *env, jobject obj, jobject j_surface); +JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkSurfaceChanged(JNIEnv *env, jobject object, jobject j_surface, jint width, jint height); +JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkResume(JNIEnv *env, jobject obj); +JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkDrawFrame(JNIEnv *env, jobject obj); +JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkPause(JNIEnv *env, jobject obj); +JNIEXPORT void JNICALL Java_org_godotengine_godot_vulkan_VkRenderer_nativeOnVkDestroy(JNIEnv *env, jobject obj); +} + +#endif // VK_RENDERER_JNI_H |