diff options
Diffstat (limited to 'platform/android')
173 files changed, 8154 insertions, 6898 deletions
diff --git a/platform/android/SCsub b/platform/android/SCsub index 3ff5b8278a..7e9dac926c 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -1,24 +1,24 @@ #!/usr/bin/env python -from detect import get_ndk_version -from distutils.version import LooseVersion - -Import('env') +Import("env") android_files = [ - 'os_android.cpp', - 'file_access_android.cpp', - 'audio_driver_opensl.cpp', - 'file_access_jandroid.cpp', - 'dir_access_jandroid.cpp', - 'thread_jandroid.cpp', - 'net_socket_android.cpp', - 'audio_driver_jandroid.cpp', - 'java_godot_lib_jni.cpp', - 'java_class_wrapper.cpp', - 'java_godot_wrapper.cpp', - 'java_godot_io_wrapper.cpp', - #'power_android.cpp' + "os_android.cpp", + "file_access_android.cpp", + "audio_driver_opensl.cpp", + "dir_access_jandroid.cpp", + "thread_jandroid.cpp", + "net_socket_android.cpp", + "audio_driver_jandroid.cpp", + "java_godot_lib_jni.cpp", + "java_class_wrapper.cpp", + "java_godot_wrapper.cpp", + "java_godot_view_wrapper.cpp", + "java_godot_io_wrapper.cpp", + "jni_utils.cpp", + "android_keys_utils.cpp", + "display_server_android.cpp", + "vulkan/vulkan_context_android.cpp", ] env_android = env.Clone() @@ -29,30 +29,38 @@ for x in android_files: env_thirdparty = env_android.Clone() env_thirdparty.disable_warnings() -android_objects.append(env_thirdparty.SharedObject('#thirdparty/misc/ifaddrs-android.cc')) +thirdparty_obj = env_thirdparty.SharedObject("#thirdparty/misc/ifaddrs-android.cc") +android_objects.append(thirdparty_obj) lib = env_android.add_shared_library("#bin/libgodot", [android_objects], SHLIBSUFFIX=env["SHLIBSUFFIX"]) -lib_arch_dir = '' -if env['android_arch'] == 'armv7': - lib_arch_dir = 'armeabi-v7a' -elif env['android_arch'] == 'arm64v8': - lib_arch_dir = 'arm64-v8a' -elif env['android_arch'] == 'x86': - lib_arch_dir = 'x86' -elif env['android_arch'] == 'x86_64': - lib_arch_dir = 'x86_64' +# Needed to force rebuilding the platform files when the thirdparty code is updated. +env.Depends(lib, thirdparty_obj) + +lib_arch_dir = "" +if env["android_arch"] == "armv7": + lib_arch_dir = "armeabi-v7a" +elif env["android_arch"] == "arm64v8": + lib_arch_dir = "arm64-v8a" +elif env["android_arch"] == "x86": + lib_arch_dir = "x86" +elif env["android_arch"] == "x86_64": + lib_arch_dir = "x86_64" else: - print('WARN: Architecture not suitable for embedding into APK; keeping .so at \\bin') + print("WARN: Architecture not suitable for embedding into APK; keeping .so at \\bin") -if lib_arch_dir != '': - if env['target'] == 'release': - lib_type_dir = 'release' +if lib_arch_dir != "": + if env["target"] == "release": + lib_type_dir = "release" else: # release_debug, debug - lib_type_dir = 'debug' + lib_type_dir = "debug" - out_dir = '#platform/android/java/lib/libs/' + lib_type_dir + '/' + lib_arch_dir - env_android.Command(out_dir + '/libgodot_android.so', '#bin/libgodot' + env['SHLIBSUFFIX'], Move("$TARGET", "$SOURCE")) + out_dir = "#platform/android/java/lib/libs/" + lib_type_dir + "/" + lib_arch_dir + env_android.Command( + out_dir + "/libgodot_android.so", "#bin/libgodot" + env["SHLIBSUFFIX"], Move("$TARGET", "$SOURCE") + ) - stl_lib_path = str(env['ANDROID_NDK_ROOT']) + '/sources/cxx-stl/llvm-libc++/libs/' + lib_arch_dir + '/libc++_shared.so' - env_android.Command(out_dir + '/libc++_shared.so', stl_lib_path, Copy("$TARGET", "$SOURCE")) + stl_lib_path = ( + str(env["ANDROID_NDK_ROOT"]) + "/sources/cxx-stl/llvm-libc++/libs/" + lib_arch_dir + "/libc++_shared.so" + ) + env_android.Command(out_dir + "/libc++_shared.so", stl_lib_path, Copy("$TARGET", "$SOURCE")) diff --git a/platform/android/android_keys_utils.cpp b/platform/android/android_keys_utils.cpp new file mode 100644 index 0000000000..5aa546c17b --- /dev/null +++ b/platform/android/android_keys_utils.cpp @@ -0,0 +1,41 @@ +/*************************************************************************/ +/* android_keys_utils.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "android_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..e0ee2888c0 --- /dev/null +++ b/platform/android/android_keys_utils.h @@ -0,0 +1,162 @@ +/*************************************************************************/ +/* android_keys_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef ANDROID_KEYS_UTILS_H +#define ANDROID_KEYS_UTILS_H + +#include <android/input.h> +#include <core/os/keyboard.h> + +struct _WinTranslatePair { + unsigned int keysym = 0; + unsigned int keycode = 0; +}; + +static _WinTranslatePair _ak_to_keycode[] = { + { KEY_TAB, AKEYCODE_TAB }, + { KEY_ENTER, AKEYCODE_ENTER }, + { KEY_SHIFT, AKEYCODE_SHIFT_LEFT }, + { KEY_SHIFT, AKEYCODE_SHIFT_RIGHT }, + { KEY_ALT, AKEYCODE_ALT_LEFT }, + { KEY_ALT, AKEYCODE_ALT_RIGHT }, + { KEY_MENU, AKEYCODE_MENU }, + { KEY_PAUSE, AKEYCODE_MEDIA_PLAY_PAUSE }, + { KEY_ESCAPE, AKEYCODE_BACK }, + { KEY_SPACE, AKEYCODE_SPACE }, + { KEY_PAGEUP, AKEYCODE_PAGE_UP }, + { KEY_PAGEDOWN, AKEYCODE_PAGE_DOWN }, + { KEY_HOME, AKEYCODE_HOME }, //(0x24) + { KEY_LEFT, AKEYCODE_DPAD_LEFT }, + { KEY_UP, AKEYCODE_DPAD_UP }, + { KEY_RIGHT, AKEYCODE_DPAD_RIGHT }, + { KEY_DOWN, AKEYCODE_DPAD_DOWN }, + { KEY_PERIODCENTERED, AKEYCODE_DPAD_CENTER }, + { KEY_BACKSPACE, AKEYCODE_DEL }, + { KEY_0, AKEYCODE_0 }, ////0 key + { KEY_1, AKEYCODE_1 }, ////1 key + { KEY_2, AKEYCODE_2 }, ////2 key + { KEY_3, AKEYCODE_3 }, ////3 key + { KEY_4, AKEYCODE_4 }, ////4 key + { KEY_5, AKEYCODE_5 }, ////5 key + { KEY_6, AKEYCODE_6 }, ////6 key + { KEY_7, AKEYCODE_7 }, ////7 key + { KEY_8, AKEYCODE_8 }, ////8 key + { KEY_9, AKEYCODE_9 }, ////9 key + { KEY_A, AKEYCODE_A }, ////A key + { KEY_B, AKEYCODE_B }, ////B key + { KEY_C, AKEYCODE_C }, ////C key + { KEY_D, AKEYCODE_D }, ////D key + { KEY_E, AKEYCODE_E }, ////E key + { KEY_F, AKEYCODE_F }, ////F key + { KEY_G, AKEYCODE_G }, ////G key + { KEY_H, AKEYCODE_H }, ////H key + { KEY_I, AKEYCODE_I }, ////I key + { KEY_J, AKEYCODE_J }, ////J key + { KEY_K, AKEYCODE_K }, ////K key + { KEY_L, AKEYCODE_L }, ////L key + { KEY_M, AKEYCODE_M }, ////M key + { KEY_N, AKEYCODE_N }, ////N key + { KEY_O, AKEYCODE_O }, ////O key + { KEY_P, AKEYCODE_P }, ////P key + { KEY_Q, AKEYCODE_Q }, ////Q key + { KEY_R, AKEYCODE_R }, ////R key + { KEY_S, AKEYCODE_S }, ////S key + { KEY_T, AKEYCODE_T }, ////T key + { KEY_U, AKEYCODE_U }, ////U key + { KEY_V, AKEYCODE_V }, ////V key + { KEY_W, AKEYCODE_W }, ////W key + { KEY_X, AKEYCODE_X }, ////X key + { KEY_Y, AKEYCODE_Y }, ////Y key + { KEY_Z, AKEYCODE_Z }, ////Z key + { KEY_HOMEPAGE, AKEYCODE_EXPLORER }, + { KEY_LAUNCH0, AKEYCODE_BUTTON_A }, + { KEY_LAUNCH1, AKEYCODE_BUTTON_B }, + { KEY_LAUNCH2, AKEYCODE_BUTTON_C }, + { KEY_LAUNCH3, AKEYCODE_BUTTON_X }, + { KEY_LAUNCH4, AKEYCODE_BUTTON_Y }, + { KEY_LAUNCH5, AKEYCODE_BUTTON_Z }, + { KEY_LAUNCH6, AKEYCODE_BUTTON_L1 }, + { KEY_LAUNCH7, AKEYCODE_BUTTON_R1 }, + { KEY_LAUNCH8, AKEYCODE_BUTTON_L2 }, + { KEY_LAUNCH9, AKEYCODE_BUTTON_R2 }, + { KEY_LAUNCHA, AKEYCODE_BUTTON_THUMBL }, + { KEY_LAUNCHB, AKEYCODE_BUTTON_THUMBR }, + { KEY_LAUNCHC, AKEYCODE_BUTTON_START }, + { KEY_LAUNCHD, AKEYCODE_BUTTON_SELECT }, + { KEY_LAUNCHE, AKEYCODE_BUTTON_MODE }, + { KEY_VOLUMEMUTE, AKEYCODE_MUTE }, + { KEY_VOLUMEDOWN, AKEYCODE_VOLUME_DOWN }, + { KEY_VOLUMEUP, AKEYCODE_VOLUME_UP }, + { KEY_BACK, AKEYCODE_MEDIA_REWIND }, + { KEY_FORWARD, AKEYCODE_MEDIA_FAST_FORWARD }, + { KEY_MEDIANEXT, AKEYCODE_MEDIA_NEXT }, + { KEY_MEDIAPREVIOUS, AKEYCODE_MEDIA_PREVIOUS }, + { KEY_MEDIASTOP, AKEYCODE_MEDIA_STOP }, + { KEY_PLUS, AKEYCODE_PLUS }, + { KEY_EQUAL, AKEYCODE_EQUALS }, // the '+' key + { KEY_COMMA, AKEYCODE_COMMA }, // the ',' key + { KEY_MINUS, AKEYCODE_MINUS }, // the '-' key + { KEY_SLASH, AKEYCODE_SLASH }, // the '/?' key + { KEY_BACKSLASH, AKEYCODE_BACKSLASH }, + { KEY_BRACKETLEFT, AKEYCODE_LEFT_BRACKET }, + { KEY_BRACKETRIGHT, AKEYCODE_RIGHT_BRACKET }, + { KEY_CONTROL, AKEYCODE_CTRL_LEFT }, + { KEY_CONTROL, AKEYCODE_CTRL_RIGHT }, + { KEY_UNKNOWN, 0 } +}; +/* +TODO: map these android key: + AKEYCODE_SOFT_LEFT = 1, + AKEYCODE_SOFT_RIGHT = 2, + AKEYCODE_CALL = 5, + AKEYCODE_ENDCALL = 6, + AKEYCODE_STAR = 17, + AKEYCODE_POUND = 18, + AKEYCODE_POWER = 26, + AKEYCODE_CAMERA = 27, + AKEYCODE_CLEAR = 28, + AKEYCODE_SYM = 63, + AKEYCODE_ENVELOPE = 65, + AKEYCODE_GRAVE = 68, + AKEYCODE_SEMICOLON = 74, + AKEYCODE_APOSTROPHE = 75, + AKEYCODE_AT = 77, + AKEYCODE_NUM = 78, + AKEYCODE_HEADSETHOOK = 79, + AKEYCODE_FOCUS = 80, // *Camera* focus + AKEYCODE_NOTIFICATION = 83, + AKEYCODE_SEARCH = 84, + AKEYCODE_PICTSYMBOLS = 94, + AKEYCODE_SWITCH_CHARSET = 95, +*/ + +unsigned int android_get_keysym(unsigned int p_code); + +#endif // ANDROID_KEYS_UTILS_H diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp new file mode 100644 index 0000000000..d3c49c6eb7 --- /dev/null +++ b/platform/android/api/api.cpp @@ -0,0 +1,88 @@ +/*************************************************************************/ +/* api.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "api.h" + +#include "core/config/engine.h" +#include "java_class_wrapper.h" +#include "jni_singleton.h" + +#if !defined(ANDROID_ENABLED) +static JavaClassWrapper *java_class_wrapper = nullptr; +#endif + +void register_android_api() { +#if !defined(ANDROID_ENABLED) + // On Android platforms, the `java_class_wrapper` instantiation and the + // `JNISingleton` registration occurs in + // `platform/android/java_godot_lib_jni.cpp#Java_org_godotengine_godot_GodotLib_setup` + java_class_wrapper = memnew(JavaClassWrapper); // Dummy + ClassDB::register_class<JNISingleton>(); +#endif + + ClassDB::register_class<JavaClass>(); + ClassDB::register_class<JavaClassWrapper>(); + Engine::get_singleton()->add_singleton(Engine::Singleton("JavaClassWrapper", JavaClassWrapper::get_singleton())); +} + +void unregister_android_api() { +#if !defined(ANDROID_ENABLED) + memdelete(java_class_wrapper); +#endif +} + +void JavaClassWrapper::_bind_methods() { + ClassDB::bind_method(D_METHOD("wrap", "name"), &JavaClassWrapper::wrap); +} + +#if !defined(ANDROID_ENABLED) + +Variant JavaClass::call(const StringName &, const Variant **, int, Callable::CallError &) { + return Variant(); +} + +JavaClass::JavaClass() { +} + +Variant JavaObject::call(const StringName &, const Variant **, int, Callable::CallError &) { + return Variant(); +} + +JavaClassWrapper *JavaClassWrapper::singleton = nullptr; + +Ref<JavaClass> JavaClassWrapper::wrap(const String &) { + return Ref<JavaClass>(); +} + +JavaClassWrapper::JavaClassWrapper() { + singleton = this; +} + +#endif diff --git a/platform/android/api/api.h b/platform/android/api/api.h new file mode 100644 index 0000000000..fe3a6734ac --- /dev/null +++ b/platform/android/api/api.h @@ -0,0 +1,37 @@ +/*************************************************************************/ +/* api.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef ANDROID_API_H +#define ANDROID_API_H + +void register_android_api(); +void unregister_android_api(); + +#endif // ANDROID_API_H diff --git a/platform/android/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h index 8075b297b0..d6c7a1abe5 100644 --- a/platform/android/java_class_wrapper.h +++ b/platform/android/api/java_class_wrapper.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,18 +31,22 @@ #ifndef JAVA_CLASS_WRAPPER_H #define JAVA_CLASS_WRAPPER_H -#include "core/reference.h" +#include "core/object/reference.h" + +#ifdef ANDROID_ENABLED #include <android/log.h> #include <jni.h> +#endif +#ifdef ANDROID_ENABLED class JavaObject; +#endif class JavaClass : public Reference { - GDCLASS(JavaClass, Reference); - enum ArgumentType { - +#ifdef ANDROID_ENABLED + enum ArgumentType{ ARG_TYPE_VOID, ARG_TYPE_BOOLEAN, ARG_TYPE_BYTE, @@ -62,24 +66,25 @@ class JavaClass : public Reference { Map<StringName, Variant> constant_map; struct MethodInfo { - - bool _static; + bool _static = false; Vector<uint32_t> param_types; Vector<StringName> param_sigs; - uint32_t return_type; + uint32_t return_type = 0; jmethodID method; }; _FORCE_INLINE_ static void _convert_to_variant_type(int p_sig, Variant::Type &r_type, float &likelihood) { - likelihood = 1.0; r_type = Variant::NIL; switch (p_sig) { - - case ARG_TYPE_VOID: r_type = Variant::NIL; break; + case ARG_TYPE_VOID: + r_type = Variant::NIL; + break; case ARG_TYPE_BOOLEAN | ARG_NUMBER_CLASS_BIT: - case ARG_TYPE_BOOLEAN: r_type = Variant::BOOL; break; + case ARG_TYPE_BOOLEAN: + r_type = Variant::BOOL; + break; case ARG_TYPE_BYTE | ARG_NUMBER_CLASS_BIT: case ARG_TYPE_BYTE: r_type = Variant::INT; @@ -107,86 +112,102 @@ class JavaClass : public Reference { break; case ARG_TYPE_FLOAT | ARG_NUMBER_CLASS_BIT: case ARG_TYPE_FLOAT: - r_type = Variant::REAL; + r_type = Variant::FLOAT; likelihood = 1.0; break; case ARG_TYPE_DOUBLE | ARG_NUMBER_CLASS_BIT: case ARG_TYPE_DOUBLE: - r_type = Variant::REAL; + r_type = Variant::FLOAT; likelihood = 0.5; break; - case ARG_TYPE_STRING: r_type = Variant::STRING; break; - case ARG_TYPE_CLASS: r_type = Variant::OBJECT; break; - case ARG_ARRAY_BIT | ARG_TYPE_VOID: r_type = Variant::NIL; break; - case ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: r_type = Variant::ARRAY; break; + case ARG_TYPE_STRING: + r_type = Variant::STRING; + break; + case ARG_TYPE_CLASS: + r_type = Variant::OBJECT; + break; + case ARG_ARRAY_BIT | ARG_TYPE_VOID: + r_type = Variant::NIL; + break; + case ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: + r_type = Variant::ARRAY; + break; case ARG_ARRAY_BIT | ARG_TYPE_BYTE: - r_type = Variant::POOL_BYTE_ARRAY; + r_type = Variant::PACKED_BYTE_ARRAY; likelihood = 1.0; break; case ARG_ARRAY_BIT | ARG_TYPE_CHAR: - r_type = Variant::POOL_BYTE_ARRAY; + r_type = Variant::PACKED_BYTE_ARRAY; likelihood = 0.5; break; case ARG_ARRAY_BIT | ARG_TYPE_SHORT: - r_type = Variant::POOL_INT_ARRAY; + r_type = Variant::PACKED_INT32_ARRAY; likelihood = 0.3; break; case ARG_ARRAY_BIT | ARG_TYPE_INT: - r_type = Variant::POOL_INT_ARRAY; + r_type = Variant::PACKED_INT32_ARRAY; likelihood = 1.0; break; case ARG_ARRAY_BIT | ARG_TYPE_LONG: - r_type = Variant::POOL_INT_ARRAY; + r_type = Variant::PACKED_INT32_ARRAY; likelihood = 0.5; break; case ARG_ARRAY_BIT | ARG_TYPE_FLOAT: - r_type = Variant::POOL_REAL_ARRAY; + r_type = Variant::PACKED_FLOAT32_ARRAY; likelihood = 1.0; break; case ARG_ARRAY_BIT | ARG_TYPE_DOUBLE: - r_type = Variant::POOL_REAL_ARRAY; + r_type = Variant::PACKED_FLOAT32_ARRAY; likelihood = 0.5; break; - case ARG_ARRAY_BIT | ARG_TYPE_STRING: r_type = Variant::POOL_STRING_ARRAY; break; - case ARG_ARRAY_BIT | ARG_TYPE_CLASS: r_type = Variant::ARRAY; break; + case ARG_ARRAY_BIT | ARG_TYPE_STRING: + r_type = Variant::PACKED_STRING_ARRAY; + break; + case ARG_ARRAY_BIT | ARG_TYPE_CLASS: + r_type = Variant::ARRAY; + break; } } _FORCE_INLINE_ static bool _convert_object_to_variant(JNIEnv *env, jobject obj, Variant &var, uint32_t p_sig); - bool _call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error, Variant &ret); + bool _call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret); friend class JavaClassWrapper; - Map<StringName, List<MethodInfo> > methods; + Map<StringName, List<MethodInfo>> methods; jclass _class; +#endif public: - virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error); + virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; JavaClass(); }; class JavaObject : public Reference { - GDCLASS(JavaObject, Reference); +#ifdef ANDROID_ENABLED Ref<JavaClass> base_class; friend class JavaClass; jobject instance; +#endif public: - virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error); + virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; +#ifdef ANDROID_ENABLED JavaObject(const Ref<JavaClass> &p_base, jobject *p_instance); ~JavaObject(); +#endif }; class JavaClassWrapper : public Object { - GDCLASS(JavaClassWrapper, Object); - Map<String, Ref<JavaClass> > class_cache; +#ifdef ANDROID_ENABLED + Map<String, Ref<JavaClass>> class_cache; friend class JavaClass; jclass activityClass; jmethodID findClass; @@ -211,6 +232,7 @@ class JavaClassWrapper : public Object { jobject classLoader; bool _get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig); +#endif static JavaClassWrapper *singleton; @@ -222,7 +244,11 @@ public: Ref<JavaClass> wrap(const String &p_class); - JavaClassWrapper(jobject p_activity = NULL); +#ifdef ANDROID_ENABLED + JavaClassWrapper(jobject p_activity = nullptr); +#else + JavaClassWrapper(); +#endif }; #endif // JAVA_CLASS_WRAPPER_H diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h new file mode 100644 index 0000000000..965eaabf81 --- /dev/null +++ b/platform/android/api/jni_singleton.h @@ -0,0 +1,223 @@ +/*************************************************************************/ +/* jni_singleton.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef JNI_SINGLETON_H +#define JNI_SINGLETON_H + +#include <core/config/engine.h> +#include <core/variant/variant.h> +#ifdef ANDROID_ENABLED +#include <platform/android/jni_utils.h> +#endif + +class JNISingleton : public Object { + GDCLASS(JNISingleton, Object); + +#ifdef ANDROID_ENABLED + struct MethodData { + jmethodID method; + Variant::Type ret_type; + Vector<Variant::Type> argtypes; + }; + + jobject instance; + Map<StringName, MethodData> method_map; +#endif + +public: + virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override { +#ifdef ANDROID_ENABLED + Map<StringName, MethodData>::Element *E = method_map.find(p_method); + + // Check the method we're looking for is in the JNISingleton map and that + // the arguments match. + bool call_error = !E || E->get().argtypes.size() != p_argcount; + if (!call_error) { + for (int i = 0; i < p_argcount; i++) { + if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) { + call_error = true; + break; + } + } + } + + if (call_error) { + // The method is not in this map, defaulting to the regular instance calls. + return Object::call(p_method, p_args, p_argcount, r_error); + } + + ERR_FAIL_COND_V(!instance, Variant()); + + r_error.error = Callable::CallError::CALL_OK; + + jvalue *v = nullptr; + + if (p_argcount) { + v = (jvalue *)alloca(sizeof(jvalue) * p_argcount); + } + + JNIEnv *env = get_jni_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(nullptr); + ERR_FAIL_V(Variant()); + } break; + } + + while (to_erase.size()) { + env->DeleteLocalRef(to_erase.front()->get()); + to_erase.pop_front(); + } + + env->PopLocalFrame(nullptr); + + return ret; +#else // ANDROID_ENABLED + + // Defaulting to the regular instance calls. + return Object::call(p_method, p_args, p_argcount, r_error); +#endif + } + +#ifdef ANDROID_ENABLED + 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; + } + + void add_signal(const StringName &p_name, const Vector<Variant::Type> &p_args) { + if (p_args.size() == 0) + ADD_SIGNAL(MethodInfo(p_name)); + else if (p_args.size() == 1) + ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"))); + else if (p_args.size() == 2) + ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"))); + else if (p_args.size() == 3) + ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"), PropertyInfo(p_args[2], "arg3"))); + else if (p_args.size() == 4) + ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"), PropertyInfo(p_args[2], "arg3"), PropertyInfo(p_args[3], "arg4"))); + else if (p_args.size() == 5) + ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"), PropertyInfo(p_args[2], "arg3"), PropertyInfo(p_args[3], "arg4"), PropertyInfo(p_args[4], "arg5"))); + } + +#endif + + JNISingleton() { +#ifdef ANDROID_ENABLED + instance = nullptr; +#endif + } +}; + +#endif // JNI_SINGLETON_H diff --git a/platform/android/audio_driver_jandroid.cpp b/platform/android/audio_driver_jandroid.cpp index 5a8e3b94da..3a2ccac481 100644 --- a/platform/android/audio_driver_jandroid.cpp +++ b/platform/android/audio_driver_jandroid.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,11 +30,11 @@ #include "audio_driver_jandroid.h" +#include "core/config/project_settings.h" #include "core/os/os.h" -#include "core/project_settings.h" #include "thread_jandroid.h" -AudioDriverAndroid *AudioDriverAndroid::s_ad = NULL; +AudioDriverAndroid *AudioDriverAndroid::s_ad = nullptr; jobject AudioDriverAndroid::io; jmethodID AudioDriverAndroid::_init_audio; @@ -46,19 +46,16 @@ jclass AudioDriverAndroid::cls; int AudioDriverAndroid::audioBufferFrames = 0; int AudioDriverAndroid::mix_rate = 44100; bool AudioDriverAndroid::quit = false; -jobject AudioDriverAndroid::audioBuffer = NULL; -void *AudioDriverAndroid::audioBufferPinned = NULL; -Mutex *AudioDriverAndroid::mutex = NULL; -int32_t *AudioDriverAndroid::audioBuffer32 = NULL; +jobject AudioDriverAndroid::audioBuffer = nullptr; +void *AudioDriverAndroid::audioBufferPinned = nullptr; +Mutex AudioDriverAndroid::mutex; +int32_t *AudioDriverAndroid::audioBuffer32 = nullptr; const char *AudioDriverAndroid::get_name() const { - return "Android"; } Error AudioDriverAndroid::init() { - - mutex = Mutex::create(); /* // TODO: pass in/return a (Java) device ID, also whether we're opening for input or output this->spec.samples = Android_JNI_OpenAudioDevice(this->spec.freq, this->spec.format == AUDIO_U8 ? 0 : 1, this->spec.channels, this->spec.samples); @@ -75,16 +72,16 @@ Error AudioDriverAndroid::init() { // __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device"); - JNIEnv *env = ThreadAndroid::get_env(); - int mix_rate = GLOBAL_DEF_RST("audio/mix_rate", 44100); + JNIEnv *env = get_jni_env(); + int mix_rate = GLOBAL_GET("audio/driver/mix_rate"); - int latency = GLOBAL_DEF_RST("audio/output_latency", 25); + int latency = GLOBAL_GET("audio/driver/output_latency"); unsigned int buffer_size = next_power_of_2(latency * mix_rate / 1000); print_verbose("Audio buffer size: " + itos(buffer_size)); audioBuffer = env->CallObjectMethod(io, _init_audio, mix_rate, buffer_size); - ERR_FAIL_COND_V(audioBuffer == NULL, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(audioBuffer == nullptr, ERR_INVALID_PARAMETER); audioBuffer = env->NewGlobalRef(audioBuffer); @@ -101,8 +98,7 @@ void AudioDriverAndroid::start() { } void AudioDriverAndroid::setup(jobject p_io) { - - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); io = p_io; jclass c = env->GetObjectClass(io); @@ -115,10 +111,8 @@ void AudioDriverAndroid::setup(jobject p_io) { } void AudioDriverAndroid::thread_func(JNIEnv *env) { - jclass cls = env->FindClass("org/godotengine/godot/Godot"); if (cls) { - cls = (jclass)env->NewGlobalRef(cls); } jfieldID fid = env->GetStaticFieldID(cls, "io", "Lorg/godotengine/godot/GodotIO;"); @@ -129,24 +123,20 @@ void AudioDriverAndroid::thread_func(JNIEnv *env) { _write_buffer = env->GetMethodID(lcls, "audioWriteShortBuffer", "([S)V"); while (!quit) { - int16_t *ptr = (int16_t *)audioBufferPinned; int fc = audioBufferFrames; - if (!s_ad->active || mutex->try_lock() != OK) { - + if (!s_ad->active || mutex.try_lock() != OK) { for (int i = 0; i < fc; i++) { ptr[i] = 0; } } else { - s_ad->audio_server_process(fc / 2, audioBuffer32); - mutex->unlock(); + mutex.unlock(); for (int i = 0; i < fc; i++) { - ptr[i] = audioBuffer32[i] >> 16; } } @@ -156,49 +146,40 @@ void AudioDriverAndroid::thread_func(JNIEnv *env) { } int AudioDriverAndroid::get_mix_rate() const { - return mix_rate; } AudioDriver::SpeakerMode AudioDriverAndroid::get_speaker_mode() const { - return SPEAKER_MODE_STEREO; } void AudioDriverAndroid::lock() { - - if (mutex) - mutex->lock(); + mutex.lock(); } void AudioDriverAndroid::unlock() { - - if (mutex) - mutex->unlock(); + mutex.unlock(); } void AudioDriverAndroid::finish() { - - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(io, _quit); if (audioBuffer) { env->DeleteGlobalRef(audioBuffer); - audioBuffer = NULL; - audioBufferPinned = NULL; + audioBuffer = nullptr; + audioBufferPinned = nullptr; } active = false; } void AudioDriverAndroid::set_pause(bool p_pause) { - - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(io, _pause, p_pause); } AudioDriverAndroid::AudioDriverAndroid() { - s_ad = this; active = false; } diff --git a/platform/android/audio_driver_jandroid.h b/platform/android/audio_driver_jandroid.h index d3d1641c20..9007fd2f81 100644 --- a/platform/android/audio_driver_jandroid.h +++ b/platform/android/audio_driver_jandroid.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -36,8 +36,7 @@ #include "java_godot_lib_jni.h" class AudioDriverAndroid : public AudioDriver { - - static Mutex *mutex; + static Mutex mutex; static AudioDriverAndroid *s_ad; static jobject io; static jmethodID _init_audio; diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp index 9e294f3f14..a1d8fb4810 100644 --- a/platform/android/audio_driver_opensl.cpp +++ b/platform/android/audio_driver_opensl.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -39,27 +39,25 @@ void AudioDriverOpenSL::_buffer_callback( SLAndroidSimpleBufferQueueItf queueItf) { - bool mix = true; if (pause) { mix = false; - } else if (mutex) { - mix = mutex->try_lock() == OK; + } else { + mix = mutex.try_lock() == OK; } if (mix) { audio_server_process(buffer_size, mixdown_buffer); } else { - int32_t *src_buff = mixdown_buffer; for (unsigned int i = 0; i < buffer_size * 2; i++) { src_buff[i] = 0; } } - if (mutex && mix) - mutex->unlock(); + if (mix) + mutex.unlock(); const int32_t *src_buff = mixdown_buffer; @@ -67,7 +65,6 @@ void AudioDriverOpenSL::_buffer_callback( last_free = (last_free + 1) % BUFFER_COUNT; for (unsigned int i = 0; i < buffer_size * 2; i++) { - ptr[i] = src_buff[i] >> 16; } @@ -77,26 +74,23 @@ void AudioDriverOpenSL::_buffer_callback( void AudioDriverOpenSL::_buffer_callbacks( SLAndroidSimpleBufferQueueItf queueItf, void *pContext) { - AudioDriverOpenSL *ad = (AudioDriverOpenSL *)pContext; ad->_buffer_callback(queueItf); } -AudioDriverOpenSL *AudioDriverOpenSL::s_ad = NULL; +AudioDriverOpenSL *AudioDriverOpenSL::s_ad = nullptr; const char *AudioDriverOpenSL::get_name() const { - return "Android"; } Error AudioDriverOpenSL::init() { - SLresult res; SLEngineOption EngineOption[] = { { (SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE } }; - res = slCreateEngine(&sl, 1, EngineOption, 0, NULL, NULL); + res = slCreateEngine(&sl, 1, EngineOption, 0, nullptr, nullptr); ERR_FAIL_COND_V_MSG(res != SL_RESULT_SUCCESS, ERR_INVALID_PARAMETER, "Could not initialize OpenSL."); res = (*sl)->Realize(sl, SL_BOOLEAN_FALSE); @@ -106,8 +100,6 @@ Error AudioDriverOpenSL::init() { } void AudioDriverOpenSL::start() { - - mutex = Mutex::create(); active = false; SLresult res; @@ -115,7 +107,6 @@ void AudioDriverOpenSL::start() { buffer_size = 1024; for (int i = 0; i < BUFFER_COUNT; i++) { - buffers[i] = memnew_arr(int16_t, buffer_size * 2); memset(buffers[i], 0, buffer_size * 4); } @@ -162,7 +153,7 @@ void AudioDriverOpenSL::start() { locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; locator_outputmix.outputMix = OutputMix; audioSink.pLocator = (void *)&locator_outputmix; - audioSink.pFormat = NULL; + audioSink.pFormat = nullptr; /* Initialize the context for Buffer queue callbacks */ //cntxt.pDataBase = (void*)&pcmData; //cntxt.pData = cntxt.pDataBase; @@ -205,11 +196,10 @@ void AudioDriverOpenSL::start() { } void AudioDriverOpenSL::_record_buffer_callback(SLAndroidSimpleBufferQueueItf queueItf) { - for (int i = 0; i < rec_buffer.size(); i++) { int32_t sample = rec_buffer[i] << 16; - capture_buffer_write(sample); - capture_buffer_write(sample); // call twice to convert to Stereo + input_buffer_write(sample); + input_buffer_write(sample); // call twice to convert to Stereo } SLresult res = (*recordBufferQueueItf)->Enqueue(recordBufferQueueItf, rec_buffer.ptrw(), rec_buffer.size() * sizeof(int16_t)); @@ -217,21 +207,19 @@ void AudioDriverOpenSL::_record_buffer_callback(SLAndroidSimpleBufferQueueItf qu } void AudioDriverOpenSL::_record_buffer_callbacks(SLAndroidSimpleBufferQueueItf queueItf, void *pContext) { - AudioDriverOpenSL *ad = (AudioDriverOpenSL *)pContext; ad->_record_buffer_callback(queueItf); } Error AudioDriverOpenSL::capture_init_device() { - SLDataLocator_IODevice loc_dev = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, - NULL + nullptr }; - SLDataSource recSource = { &loc_dev, NULL }; + SLDataSource recSource = { &loc_dev, nullptr }; SLDataLocator_AndroidSimpleBufferQueue loc_bq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, @@ -280,7 +268,7 @@ Error AudioDriverOpenSL::capture_init_device() { const int rec_buffer_frames = 2048; rec_buffer.resize(rec_buffer_frames); - capture_buffer_init(rec_buffer_frames); + input_buffer_init(rec_buffer_frames); res = (*recordBufferQueueItf)->Enqueue(recordBufferQueueItf, rec_buffer.ptrw(), rec_buffer.size() * sizeof(int16_t)); ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); @@ -292,7 +280,6 @@ Error AudioDriverOpenSL::capture_init_device() { } Error AudioDriverOpenSL::capture_start() { - if (OS::get_singleton()->request_permission("RECORD_AUDIO")) { return capture_init_device(); } @@ -301,7 +288,6 @@ Error AudioDriverOpenSL::capture_start() { } Error AudioDriverOpenSL::capture_stop() { - SLuint32 state; SLresult res = (*recordItf)->GetRecordState(recordItf, &state); ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN); @@ -318,34 +304,28 @@ Error AudioDriverOpenSL::capture_stop() { } int AudioDriverOpenSL::get_mix_rate() const { - return 44100; // hardcoded for Android, as selected by SL_SAMPLINGRATE_44_1 } AudioDriver::SpeakerMode AudioDriverOpenSL::get_speaker_mode() const { - return SPEAKER_MODE_STEREO; } void AudioDriverOpenSL::lock() { - - if (active && mutex) - mutex->lock(); + if (active) + mutex.lock(); } void AudioDriverOpenSL::unlock() { - - if (active && mutex) - mutex->unlock(); + if (active) + mutex.unlock(); } void AudioDriverOpenSL::finish() { - (*sl)->Destroy(sl); } void AudioDriverOpenSL::set_pause(bool p_pause) { - pause = p_pause; if (active) { @@ -359,7 +339,4 @@ void AudioDriverOpenSL::set_pause(bool p_pause) { AudioDriverOpenSL::AudioDriverOpenSL() { s_ad = this; - mutex = Mutex::create(); //NULL; - pause = false; - active = false; } diff --git a/platform/android/audio_driver_opensl.h b/platform/android/audio_driver_opensl.h index 57d9b30af6..e3efaddba2 100644 --- a/platform/android/audio_driver_opensl.h +++ b/platform/android/audio_driver_opensl.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -38,21 +38,19 @@ #include <SLES/OpenSLES_Android.h> class AudioDriverOpenSL : public AudioDriver { - - bool active; - Mutex *mutex; + bool active = false; + Mutex mutex; enum { - BUFFER_COUNT = 2 }; - bool pause; + bool pause = false; - uint32_t buffer_size; - int16_t *buffers[BUFFER_COUNT]; - int32_t *mixdown_buffer; - int last_free; + uint32_t buffer_size = 0; + int16_t *buffers[BUFFER_COUNT] = {}; + int32_t *mixdown_buffer = nullptr; + int last_free = 0; Vector<int16_t> rec_buffer; diff --git a/platform/android/detect.py b/platform/android/detect.py index 8b62360888..5f0fcc9b77 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -13,7 +13,7 @@ def get_name(): def can_build(): - return ("ANDROID_NDK_ROOT" in os.environ) + return ("ANDROID_SDK_ROOT" in os.environ) or ("ANDROID_HOME" in os.environ) def get_platform(platform): @@ -24,33 +24,77 @@ def get_opts(): from SCons.Variables import BoolVariable, EnumVariable return [ - ('ANDROID_NDK_ROOT', 'Path to the Android NDK', os.environ.get("ANDROID_NDK_ROOT", 0)), - ('ndk_platform', 'Target platform (android-<api>, e.g. "android-18")', "android-18"), - EnumVariable('android_arch', 'Target architecture', "armv7", ('armv7', 'arm64v8', 'x86', 'x86_64')), - BoolVariable('android_neon', 'Enable NEON support (armv7 only)', True), + ("ANDROID_NDK_ROOT", "Path to the Android NDK", get_android_ndk_root()), + ("ANDROID_SDK_ROOT", "Path to the Android SDK", get_android_sdk_root()), + ("ndk_platform", 'Target platform (android-<api>, e.g. "android-24")', "android-24"), + EnumVariable("android_arch", "Target architecture", "armv7", ("armv7", "arm64v8", "x86", "x86_64")), + BoolVariable("android_neon", "Enable NEON support (armv7 only)", True), ] +# Return the ANDROID_SDK_ROOT environment variable. +# While ANDROID_HOME has been deprecated, it's used as a fallback for backward +# compatibility purposes. +def get_android_sdk_root(): + if "ANDROID_SDK_ROOT" in os.environ: + return os.environ.get("ANDROID_SDK_ROOT", 0) + else: + return os.environ.get("ANDROID_HOME", 0) + + +# Return the ANDROID_NDK_ROOT environment variable. +# We generate one for this build using the ANDROID_SDK_ROOT env +# variable and the project ndk version. +# If the env variable is already defined, we override it with +# our own to match what the project expects. +def get_android_ndk_root(): + return get_android_sdk_root() + "/ndk/" + get_project_ndk_version() + + def get_flags(): return [ - ('tools', False), + ("tools", False), ] def create(env): - tools = env['TOOLS'] + tools = env["TOOLS"] if "mingw" in tools: - tools.remove('mingw') + tools.remove("mingw") if "applelink" in tools: tools.remove("applelink") - env.Tool('gcc') + env.Tool("gcc") return env.Clone(tools=tools) +# Check if ANDROID_NDK_ROOT is valid. +# If not, install the ndk using ANDROID_SDK_ROOT and sdkmanager. +def install_ndk_if_needed(env): + print("Checking for Android NDK...") + env_ndk_version = get_env_ndk_version(env["ANDROID_NDK_ROOT"]) + if env_ndk_version is None: + # Reinstall the ndk and update ANDROID_NDK_ROOT. + print("Installing Android NDK...") + if env["ANDROID_SDK_ROOT"] is None: + raise Exception("Invalid ANDROID_SDK_ROOT environment variable.") + + import subprocess + + extension = ".bat" if os.name == "nt" else "" + sdkmanager_path = env["ANDROID_SDK_ROOT"] + "/cmdline-tools/latest/bin/sdkmanager" + extension + ndk_download_args = "ndk;" + get_project_ndk_version() + subprocess.check_call([sdkmanager_path, ndk_download_args]) + + env["ANDROID_NDK_ROOT"] = env["ANDROID_SDK_ROOT"] + "/ndk/" + get_project_ndk_version() + print("ANDROID_NDK_ROOT: " + env["ANDROID_NDK_ROOT"]) + + def configure(env): + install_ndk_if_needed(env) + # Workaround for MinGW. See: # http://www.scons.org/wiki/LongCmdLinesOnWin32 - if (os.name == "nt"): + if os.name == "nt": import subprocess @@ -58,8 +102,15 @@ def configure(env): # print("SPAWNED : " + cmdline) startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, startupinfo=startupinfo, shell=False, env=env) + proc = subprocess.Popen( + cmdline, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + startupinfo=startupinfo, + shell=False, + env=env, + ) data, err = proc.communicate() rv = proc.wait() if rv: @@ -70,7 +121,7 @@ def configure(env): def mySpawn(sh, escape, cmd, args, env): - newargs = ' '.join(args[1:]) + newargs = " ".join(args[1:]) cmdline = cmd + " " + newargs rv = 0 @@ -85,50 +136,56 @@ def configure(env): return rv - env['SPAWN'] = mySpawn + env["SPAWN"] = mySpawn # Architecture - if env['android_arch'] not in ['armv7', 'arm64v8', 'x86', 'x86_64']: - env['android_arch'] = 'armv7' + if env["android_arch"] not in ["armv7", "arm64v8", "x86", "x86_64"]: + env["android_arch"] = "armv7" neon_text = "" - if env["android_arch"] == "armv7" and env['android_neon']: + if env["android_arch"] == "armv7" and env["android_neon"]: neon_text = " (with NEON)" - print("Building for Android (" + env['android_arch'] + ")" + neon_text) + print("Building for Android, platform " + env["ndk_platform"] + " (" + env["android_arch"] + ")" + neon_text) can_vectorize = True - if env['android_arch'] == 'x86': - env['ARCH'] = 'arch-x86' + if env["android_arch"] == "x86": + env["ARCH"] = "arch-x86" env.extra_suffix = ".x86" + env.extra_suffix target_subpath = "x86-4.9" abi_subpath = "i686-linux-android" arch_subpath = "x86" env["x86_libtheora_opt_gcc"] = True - if env['android_arch'] == 'x86_64': + if env["android_arch"] == "x86_64": if get_platform(env["ndk_platform"]) < 21: - print("WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21") + print( + "WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting" + " ndk_platform=android-21" + ) env["ndk_platform"] = "android-21" - env['ARCH'] = 'arch-x86_64' + env["ARCH"] = "arch-x86_64" env.extra_suffix = ".x86_64" + env.extra_suffix target_subpath = "x86_64-4.9" abi_subpath = "x86_64-linux-android" arch_subpath = "x86_64" env["x86_libtheora_opt_gcc"] = True elif env["android_arch"] == "armv7": - env['ARCH'] = 'arch-arm' + env["ARCH"] = "arch-arm" target_subpath = "arm-linux-androideabi-4.9" abi_subpath = "arm-linux-androideabi" arch_subpath = "armeabi-v7a" - if env['android_neon']: + if env["android_neon"]: env.extra_suffix = ".armv7.neon" + env.extra_suffix else: env.extra_suffix = ".armv7" + env.extra_suffix elif env["android_arch"] == "arm64v8": if get_platform(env["ndk_platform"]) < 21: - print("WARNING: android_arch=arm64v8 is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21") + print( + "WARNING: android_arch=arm64v8 is not supported by ndk_platform lower than android-21; setting" + " ndk_platform=android-21" + ) env["ndk_platform"] = "android-21" - env['ARCH'] = 'arch-arm64' + env["ARCH"] = "arch-arm64" target_subpath = "aarch64-linux-android-4.9" abi_subpath = "aarch64-linux-android" arch_subpath = "arm64-v8a" @@ -136,86 +193,80 @@ def configure(env): # Build type - if (env["target"].startswith("release")): - if (env["optimize"] == "speed"): # optimize for speed (default) - env.Append(LINKFLAGS=['-O2']) - env.Append(CCFLAGS=['-O2', '-fomit-frame-pointer']) - env.Append(CPPDEFINES=['NDEBUG']) + if env["target"].startswith("release"): + if env["optimize"] == "speed": # optimize for speed (default) + env.Append(LINKFLAGS=["-O2"]) + env.Append(CCFLAGS=["-O2", "-fomit-frame-pointer"]) + env.Append(CPPDEFINES=["NDEBUG"]) else: # optimize for size - env.Append(CCFLAGS=['-Os']) - env.Append(CPPDEFINES=['NDEBUG']) - env.Append(LINKFLAGS=['-Os']) - - if (can_vectorize): - env.Append(CCFLAGS=['-ftree-vectorize']) - if (env["target"] == "release_debug"): - env.Append(CPPDEFINES=['DEBUG_ENABLED']) - elif (env["target"] == "debug"): - env.Append(LINKFLAGS=['-O0']) - env.Append(CCFLAGS=['-O0', '-g', '-fno-limit-debug-info']) - env.Append(CPPDEFINES=['_DEBUG', 'DEBUG_ENABLED', 'DEBUG_MEMORY_ENABLED']) - env.Append(CPPFLAGS=['-UNDEBUG']) + env.Append(CCFLAGS=["-Os"]) + env.Append(CPPDEFINES=["NDEBUG"]) + env.Append(LINKFLAGS=["-Os"]) + + if can_vectorize: + env.Append(CCFLAGS=["-ftree-vectorize"]) + if env["target"] == "release_debug": + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + elif env["target"] == "debug": + env.Append(LINKFLAGS=["-O0"]) + env.Append(CCFLAGS=["-O0", "-g", "-fno-limit-debug-info"]) + env.Append(CPPDEFINES=["_DEBUG", "DEBUG_ENABLED"]) + env.Append(CPPFLAGS=["-UNDEBUG"]) # Compiler configuration - env['SHLIBSUFFIX'] = '.so' + env["SHLIBSUFFIX"] = ".so" - if env['PLATFORM'] == 'win32': - env.Tool('gcc') + if env["PLATFORM"] == "win32": + env.Tool("gcc") env.use_windows_spawn_fix() - mt_link = True - if (sys.platform.startswith("linux")): + if sys.platform.startswith("linux"): host_subpath = "linux-x86_64" - elif (sys.platform.startswith("darwin")): + elif sys.platform.startswith("darwin"): host_subpath = "darwin-x86_64" - elif (sys.platform.startswith('win')): - if (platform.machine().endswith('64')): + elif sys.platform.startswith("win"): + if platform.machine().endswith("64"): host_subpath = "windows-x86_64" else: - mt_link = False host_subpath = "windows" - if env["android_arch"] == "arm64v8": - mt_link = False - compiler_path = env["ANDROID_NDK_ROOT"] + "/toolchains/llvm/prebuilt/" + host_subpath + "/bin" gcc_toolchain_path = env["ANDROID_NDK_ROOT"] + "/toolchains/" + target_subpath + "/prebuilt/" + host_subpath tools_path = gcc_toolchain_path + "/" + abi_subpath + "/bin" # For Clang to find NDK tools in preference of those system-wide - env.PrependENVPath('PATH', tools_path) + env.PrependENVPath("PATH", tools_path) ccache_path = os.environ.get("CCACHE") if ccache_path is None: - env['CC'] = compiler_path + '/clang' - env['CXX'] = compiler_path + '/clang++' + env["CC"] = compiler_path + "/clang" + env["CXX"] = compiler_path + "/clang++" else: # there aren't any ccache wrappers available for Android, # to enable caching we need to prepend the path to the ccache binary - env['CC'] = ccache_path + ' ' + compiler_path + '/clang' - env['CXX'] = ccache_path + ' ' + compiler_path + '/clang++' - env['AR'] = tools_path + "/ar" - env['RANLIB'] = tools_path + "/ranlib" - env['AS'] = tools_path + "/as" + env["CC"] = ccache_path + " " + compiler_path + "/clang" + env["CXX"] = ccache_path + " " + compiler_path + "/clang++" + env["AR"] = tools_path + "/ar" + env["RANLIB"] = tools_path + "/ranlib" + env["AS"] = tools_path + "/as" - common_opts = ['-fno-integrated-as', '-gcc-toolchain', gcc_toolchain_path] + common_opts = ["-fno-integrated-as", "-gcc-toolchain", gcc_toolchain_path] # Compile flags 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']: - env.Append(CXXFLAGS=['-frtti']) + if env["tools"] or env["builtin_icu"]: + env.Append(CXXFLAGS=["-frtti"]) else: - env.Append(CXXFLAGS=['-fno-rtti', '-fno-exceptions']) + env.Append(CXXFLAGS=["-fno-rtti", "-fno-exceptions"]) # Don't use dynamic_cast, necessary with no-rtti. - env.Append(CPPDEFINES=['NO_SAFE_CAST']) + env.Append(CPPDEFINES=["NO_SAFE_CAST"]) - lib_sysroot = env["ANDROID_NDK_ROOT"] + "/platforms/" + env['ndk_platform'] + "/" + env['ARCH'] + lib_sysroot = env["ANDROID_NDK_ROOT"] + "/platforms/" + env["ndk_platform"] + "/" + env["ARCH"] # Using NDK unified headers (NDK r15+) sysroot = env["ANDROID_NDK_ROOT"] + "/sysroot" @@ -223,70 +274,107 @@ def configure(env): env.Append(CPPFLAGS=["-isystem", sysroot + "/usr/include/" + abi_subpath]) env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/android/support/include"]) # For unified headers this define has to be set manually - env.Append(CPPDEFINES=[('__ANDROID_API__', str(get_platform(env['ndk_platform'])))]) - - env.Append(CCFLAGS='-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing'.split()) - env.Append(CPPDEFINES=['NO_STATVFS', 'GLES_ENABLED']) - - env['neon_enabled'] = False - if env['android_arch'] == 'x86': - target_opts = ['-target', 'i686-none-linux-android'] + env.Append(CPPDEFINES=[("__ANDROID_API__", str(get_platform(env["ndk_platform"])))]) + + env.Append( + CCFLAGS=( + "-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden" + " -fno-strict-aliasing".split() + ) + ) + env.Append(CPPDEFINES=["NO_STATVFS", "GLES_ENABLED"]) + + env["neon_enabled"] = False + if env["android_arch"] == "x86": + target_opts = ["-target", "i686-none-linux-android"] # The NDK adds this if targeting API < 21, so we can drop it when Godot targets it at least - env.Append(CCFLAGS=['-mstackrealign']) + env.Append(CCFLAGS=["-mstackrealign"]) - elif env['android_arch'] == 'x86_64': - target_opts = ['-target', 'x86_64-none-linux-android'] + elif env["android_arch"] == "x86_64": + target_opts = ["-target", "x86_64-none-linux-android"] elif env["android_arch"] == "armv7": - target_opts = ['-target', 'armv7-none-linux-androideabi'] - env.Append(CCFLAGS='-march=armv7-a -mfloat-abi=softfp'.split()) - env.Append(CPPDEFINES=['__ARM_ARCH_7__', '__ARM_ARCH_7A__']) - if env['android_neon']: - env['neon_enabled'] = True - env.Append(CCFLAGS=['-mfpu=neon']) - env.Append(CPPDEFINES=['__ARM_NEON__']) + target_opts = ["-target", "armv7-none-linux-androideabi"] + env.Append(CCFLAGS="-march=armv7-a -mfloat-abi=softfp".split()) + env.Append(CPPDEFINES=["__ARM_ARCH_7__", "__ARM_ARCH_7A__"]) + if env["android_neon"]: + env["neon_enabled"] = True + env.Append(CCFLAGS=["-mfpu=neon"]) + env.Append(CPPDEFINES=["__ARM_NEON__"]) else: - env.Append(CCFLAGS=['-mfpu=vfpv3-d16']) + env.Append(CCFLAGS=["-mfpu=vfpv3-d16"]) elif env["android_arch"] == "arm64v8": - target_opts = ['-target', 'aarch64-none-linux-android'] - env.Append(CCFLAGS=['-mfix-cortex-a53-835769']) - env.Append(CPPDEFINES=['__ARM_ARCH_8A__']) + target_opts = ["-target", "aarch64-none-linux-android"] + env.Append(CCFLAGS=["-mfix-cortex-a53-835769"]) + env.Append(CPPDEFINES=["__ARM_ARCH_8A__"]) env.Append(CCFLAGS=target_opts) env.Append(CCFLAGS=common_opts) # Link flags - ndk_version = get_ndk_version(env["ANDROID_NDK_ROOT"]) + ndk_version = get_env_ndk_version(env["ANDROID_NDK_ROOT"]) if ndk_version != None and LooseVersion(ndk_version) >= LooseVersion("17.1.4828580"): - env.Append(LINKFLAGS=['-Wl,--exclude-libs,libgcc.a', '-Wl,--exclude-libs,libatomic.a', '-nostdlib++']) + env.Append(LINKFLAGS=["-Wl,--exclude-libs,libgcc.a", "-Wl,--exclude-libs,libatomic.a", "-nostdlib++"]) else: - env.Append(LINKFLAGS=[env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/libandroid_support.a"]) - env.Append(LINKFLAGS=['-shared', '--sysroot=' + lib_sysroot, '-Wl,--warn-shared-textrel']) + env.Append( + LINKFLAGS=[ + env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/libandroid_support.a" + ] + ) + env.Append(LINKFLAGS=["-shared", "--sysroot=" + lib_sysroot, "-Wl,--warn-shared-textrel"]) env.Append(LIBPATH=[env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/"]) - env.Append(LINKFLAGS=[env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/libc++_shared.so"]) + env.Append( + LINKFLAGS=[env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/libs/" + arch_subpath + "/libc++_shared.so"] + ) if env["android_arch"] == "armv7": - env.Append(LINKFLAGS='-Wl,--fix-cortex-a8'.split()) - env.Append(LINKFLAGS='-Wl,--no-undefined -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now'.split()) - env.Append(LINKFLAGS='-Wl,-soname,libgodot_android.so -Wl,--gc-sections'.split()) + env.Append(LINKFLAGS="-Wl,--fix-cortex-a8".split()) + env.Append(LINKFLAGS="-Wl,--no-undefined -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now".split()) + env.Append(LINKFLAGS="-Wl,-soname,libgodot_android.so -Wl,--gc-sections".split()) env.Append(LINKFLAGS=target_opts) env.Append(LINKFLAGS=common_opts) - env.Append(LIBPATH=[env["ANDROID_NDK_ROOT"] + '/toolchains/' + target_subpath + '/prebuilt/' + - host_subpath + '/lib/gcc/' + abi_subpath + '/4.9.x']) - env.Append(LIBPATH=[env["ANDROID_NDK_ROOT"] + - '/toolchains/' + target_subpath + '/prebuilt/' + host_subpath + '/' + abi_subpath + '/lib']) - - env.Prepend(CPPPATH=['#platform/android']) - env.Append(CPPDEFINES=['ANDROID_ENABLED', 'UNIX_ENABLED', 'NO_FCNTL']) - env.Append(LIBS=['OpenSLES', 'EGL', 'GLESv3', 'GLESv2', 'android', 'log', 'z', 'dl']) + env.Append( + LIBPATH=[ + env["ANDROID_NDK_ROOT"] + + "/toolchains/" + + target_subpath + + "/prebuilt/" + + host_subpath + + "/lib/gcc/" + + abi_subpath + + "/4.9.x" + ] + ) + env.Append( + LIBPATH=[ + env["ANDROID_NDK_ROOT"] + + "/toolchains/" + + target_subpath + + "/prebuilt/" + + host_subpath + + "/" + + abi_subpath + + "/lib" + ] + ) + + env.Prepend(CPPPATH=["#platform/android"]) + env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED", "VULKAN_ENABLED", "NO_FCNTL"]) + env.Append(LIBS=["OpenSLES", "EGL", "GLESv2", "vulkan", "android", "log", "z", "dl"]) + + +# Return the project NDK version. +# This is kept in sync with the value in 'platform/android/java/app/config.gradle'. +def get_project_ndk_version(): + return "21.4.7075529" # Return NDK version string in source.properties (adapted from the Chromium project). -def get_ndk_version(path): +def get_env_ndk_version(path): if path is None: return None prop_file_path = os.path.join(path, "source.properties") @@ -296,6 +384,6 @@ def get_ndk_version(path): key_value = list(map(lambda x: x.strip(), line.split("="))) if key_value[0] == "Pkg.Revision": return key_value[1] - except: + except Exception: print("Could not read source prop file '%s'" % prop_file_path) return None diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index f52b511522..f8ac29c738 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,27 +29,25 @@ /*************************************************************************/ #include "dir_access_jandroid.h" -#include "core/print_string.h" -#include "file_access_jandroid.h" +#include "core/string/print_string.h" +#include "file_access_android.h" #include "string_android.h" #include "thread_jandroid.h" -jobject DirAccessJAndroid::io = NULL; -jclass DirAccessJAndroid::cls = NULL; -jmethodID DirAccessJAndroid::_dir_open = NULL; -jmethodID DirAccessJAndroid::_dir_next = NULL; -jmethodID DirAccessJAndroid::_dir_close = NULL; -jmethodID DirAccessJAndroid::_dir_is_dir = NULL; +jobject DirAccessJAndroid::io = nullptr; +jclass DirAccessJAndroid::cls = nullptr; +jmethodID DirAccessJAndroid::_dir_open = nullptr; +jmethodID DirAccessJAndroid::_dir_next = nullptr; +jmethodID DirAccessJAndroid::_dir_close = nullptr; +jmethodID DirAccessJAndroid::_dir_is_dir = nullptr; DirAccess *DirAccessJAndroid::create_fs() { - return memnew(DirAccessJAndroid); } Error DirAccessJAndroid::list_dir_begin() { - list_dir_end(); - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring js = env->NewStringUTF(current_dir.utf8().get_data()); int res = env->CallIntMethod(io, _dir_open, js); @@ -62,10 +60,9 @@ Error DirAccessJAndroid::list_dir_begin() { } String DirAccessJAndroid::get_next() { - ERR_FAIL_COND_V(id == 0, ""); - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring str = (jstring)env->CallObjectMethod(io, _dir_next, id); if (!str) return ""; @@ -76,40 +73,34 @@ String DirAccessJAndroid::get_next() { } bool DirAccessJAndroid::current_is_dir() const { - - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); return env->CallBooleanMethod(io, _dir_is_dir, id); } bool DirAccessJAndroid::current_is_hidden() const { - return current != "." && current != ".." && current.begins_with("."); } void DirAccessJAndroid::list_dir_end() { - if (id == 0) return; - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(io, _dir_close, id); id = 0; } int DirAccessJAndroid::get_drive_count() { - return 0; } String DirAccessJAndroid::get_drive(int p_drive) { - return ""; } Error DirAccessJAndroid::change_dir(String p_dir) { - - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); if (p_dir == "" || p_dir == "." || (p_dir == ".." && current_dir == "")) return OK; @@ -144,20 +135,18 @@ 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; } bool DirAccessJAndroid::file_exists(String p_file) { - String sd; if (current_dir == "") sd = p_file; else sd = current_dir.plus_file(p_file); - FileAccessJAndroid *f = memnew(FileAccessJAndroid); + FileAccessAndroid *f = memnew(FileAccessAndroid); bool exists = f->file_exists(sd); memdelete(f); @@ -165,8 +154,7 @@ bool DirAccessJAndroid::file_exists(String p_file) { } bool DirAccessJAndroid::dir_exists(String p_dir) { - - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); String sd; @@ -198,34 +186,28 @@ bool DirAccessJAndroid::dir_exists(String p_dir) { } Error DirAccessJAndroid::make_dir(String p_dir) { - ERR_FAIL_V(ERR_UNAVAILABLE); } Error DirAccessJAndroid::rename(String p_from, String p_to) { - ERR_FAIL_V(ERR_UNAVAILABLE); } Error DirAccessJAndroid::remove(String p_name) { - ERR_FAIL_V(ERR_UNAVAILABLE); } String DirAccessJAndroid::get_filesystem_type() const { - return "APK"; } //FileType get_file_type() const; size_t DirAccessJAndroid::get_space_left() { - return 0; } void DirAccessJAndroid::setup(jobject p_io) { - - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); io = p_io; jclass c = env->GetObjectClass(io); @@ -240,11 +222,9 @@ void DirAccessJAndroid::setup(jobject p_io) { } DirAccessJAndroid::DirAccessJAndroid() { - id = 0; } DirAccessJAndroid::~DirAccessJAndroid() { - list_dir_end(); } diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h index caeb4b58b9..fed468d051 100644 --- a/platform/android/dir_access_jandroid.h +++ b/platform/android/dir_access_jandroid.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -36,7 +36,6 @@ #include <stdio.h> class DirAccessJAndroid : public DirAccess { - //AAssetDir* aad; static jobject io; @@ -65,7 +64,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/display_server_android.cpp b/platform/android/display_server_android.cpp new file mode 100644 index 0000000000..5f7e5eaa83 --- /dev/null +++ b/platform/android/display_server_android.cpp @@ -0,0 +1,873 @@ +/*************************************************************************/ +/* display_server_android.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "display_server_android.h" + +#include "android_keys_utils.h" +#include "core/config/project_settings.h" +#include "java_godot_io_wrapper.h" +#include "java_godot_wrapper.h" +#include "os_android.h" + +#include <android/input.h> + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_device_vulkan.h" +#include "platform/android/vulkan/vulkan_context_android.h" +#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" +#endif + +DisplayServerAndroid *DisplayServerAndroid::get_singleton() { + return (DisplayServerAndroid *)DisplayServer::get_singleton(); +} + +bool DisplayServerAndroid::has_feature(Feature p_feature) const { + switch (p_feature) { + //case FEATURE_CONSOLE_WINDOW: + //case FEATURE_CURSOR_SHAPE: + //case FEATURE_CUSTOM_CURSOR_SHAPE: + //case FEATURE_GLOBAL_MENU: + //case FEATURE_HIDPI: + //case FEATURE_ICON: + //case FEATURE_IME: + case FEATURE_MOUSE: + //case FEATURE_MOUSE_WARP: + //case FEATURE_NATIVE_DIALOG: + //case FEATURE_NATIVE_ICON: + //case FEATURE_NATIVE_VIDEO: + //case FEATURE_WINDOW_TRANSPARENCY: + case FEATURE_CLIPBOARD: + case FEATURE_KEEP_SCREEN_ON: + case FEATURE_ORIENTATION: + case FEATURE_TOUCHSCREEN: + case FEATURE_VIRTUAL_KEYBOARD: + return true; + default: + return false; + } +} + +String DisplayServerAndroid::get_name() const { + return "Android"; +} + +void DisplayServerAndroid::clipboard_set(const String &p_text) { + GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); + ERR_FAIL_COND(!godot_java); + + if (godot_java->has_set_clipboard()) { + godot_java->set_clipboard(p_text); + } else { + DisplayServer::clipboard_set(p_text); + } +} + +String DisplayServerAndroid::clipboard_get() const { + GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); + ERR_FAIL_COND_V(!godot_java, String()); + + if (godot_java->has_get_clipboard()) { + return godot_java->get_clipboard(); + } else { + return DisplayServer::clipboard_get(); + } +} + +void DisplayServerAndroid::screen_set_keep_on(bool p_enable) { + GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); + ERR_FAIL_COND(!godot_java); + + godot_java->set_keep_screen_on(p_enable); + keep_screen_on = p_enable; +} + +bool DisplayServerAndroid::screen_is_kept_on() const { + return keep_screen_on; +} + +void DisplayServerAndroid::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) { + GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java(); + ERR_FAIL_COND(!godot_io_java); + + godot_io_java->set_screen_orientation(p_orientation); +} + +DisplayServer::ScreenOrientation DisplayServerAndroid::screen_get_orientation(int p_screen) const { + GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java(); + ERR_FAIL_COND_V(!godot_io_java, SCREEN_LANDSCAPE); + + return (ScreenOrientation)godot_io_java->get_screen_orientation(); +} + +int DisplayServerAndroid::get_screen_count() const { + return 1; +} + +Point2i DisplayServerAndroid::screen_get_position(int p_screen) const { + return Point2i(0, 0); +} + +Size2i DisplayServerAndroid::screen_get_size(int p_screen) const { + return OS_Android::get_singleton()->get_display_size(); +} + +Rect2i DisplayServerAndroid::screen_get_usable_rect(int p_screen) const { + GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java(); + ERR_FAIL_COND_V(!godot_io_java, Rect2i()); + int xywh[4]; + godot_io_java->screen_get_usable_rect(xywh); + return Rect2i(xywh[0], xywh[1], xywh[2], xywh[3]); +} + +int DisplayServerAndroid::screen_get_dpi(int p_screen) const { + GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java(); + ERR_FAIL_COND_V(!godot_io_java, 0); + + return godot_io_java->get_screen_dpi(); +} + +bool DisplayServerAndroid::screen_is_touchscreen(int p_screen) const { + return true; +} + +void DisplayServerAndroid::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) { + GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java(); + ERR_FAIL_COND(!godot_io_java); + + if (godot_io_java->has_vk()) { + godot_io_java->show_vk(p_existing_text, p_multiline, p_max_length, p_cursor_start, p_cursor_end); + } else { + ERR_PRINT("Virtual keyboard not available"); + } +} + +void DisplayServerAndroid::virtual_keyboard_hide() { + GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java(); + ERR_FAIL_COND(!godot_io_java); + + if (godot_io_java->has_vk()) { + godot_io_java->hide_vk(); + } else { + ERR_PRINT("Virtual keyboard not available"); + } +} + +int DisplayServerAndroid::virtual_keyboard_get_height() const { + GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java(); + ERR_FAIL_COND_V(!godot_io_java, 0); + + return godot_io_java->get_vk_height(); +} + +void DisplayServerAndroid::window_set_window_event_callback(const Callable &p_callable, DisplayServer::WindowID p_window) { + window_event_callback = p_callable; +} + +void DisplayServerAndroid::window_set_input_event_callback(const Callable &p_callable, DisplayServer::WindowID p_window) { + input_event_callback = p_callable; +} + +void DisplayServerAndroid::window_set_input_text_callback(const Callable &p_callable, DisplayServer::WindowID p_window) { + input_text_callback = p_callable; +} + +void DisplayServerAndroid::window_set_rect_changed_callback(const Callable &p_callable, DisplayServer::WindowID p_window) { + // Not supported on Android. +} + +void DisplayServerAndroid::window_set_drop_files_callback(const Callable &p_callable, DisplayServer::WindowID p_window) { + // Not supported on Android. +} + +void DisplayServerAndroid::_window_callback(const Callable &p_callable, const Variant &p_arg) const { + if (!p_callable.is_null()) { + const Variant *argp = &p_arg; + Variant ret; + Callable::CallError ce; + p_callable.call((const Variant **)&argp, 1, ret, ce); + } +} + +void DisplayServerAndroid::send_window_event(DisplayServer::WindowEvent p_event) const { + _window_callback(window_event_callback, int(p_event)); +} + +void DisplayServerAndroid::send_input_event(const Ref<InputEvent> &p_event) const { + _window_callback(input_event_callback, p_event); +} + +void DisplayServerAndroid::send_input_text(const String &p_text) const { + _window_callback(input_text_callback, p_text); +} + +void DisplayServerAndroid::_dispatch_input_events(const Ref<InputEvent> &p_event) { + DisplayServerAndroid::get_singleton()->send_input_event(p_event); +} + +Vector<DisplayServer::WindowID> DisplayServerAndroid::get_window_list() const { + Vector<WindowID> ret; + ret.push_back(MAIN_WINDOW_ID); + return ret; +} + +DisplayServer::WindowID DisplayServerAndroid::get_window_at_screen_position(const Point2i &p_position) const { + return MAIN_WINDOW_ID; +} + +void DisplayServerAndroid::window_attach_instance_id(ObjectID p_instance, DisplayServer::WindowID p_window) { + window_attached_instance_id = p_instance; +} + +ObjectID DisplayServerAndroid::window_get_attached_instance_id(DisplayServer::WindowID p_window) const { + return window_attached_instance_id; +} + +void DisplayServerAndroid::window_set_title(const String &p_title, DisplayServer::WindowID p_window) { + // Not supported on Android. +} + +int DisplayServerAndroid::window_get_current_screen(DisplayServer::WindowID p_window) const { + return SCREEN_OF_MAIN_WINDOW; +} + +void DisplayServerAndroid::window_set_current_screen(int p_screen, DisplayServer::WindowID p_window) { + // Not supported on Android. +} + +Point2i DisplayServerAndroid::window_get_position(DisplayServer::WindowID p_window) const { + return Point2i(); +} + +void DisplayServerAndroid::window_set_position(const Point2i &p_position, DisplayServer::WindowID p_window) { + // Not supported on Android. +} + +void DisplayServerAndroid::window_set_transient(DisplayServer::WindowID p_window, DisplayServer::WindowID p_parent) { + // Not supported on Android. +} + +void DisplayServerAndroid::window_set_max_size(const Size2i p_size, DisplayServer::WindowID p_window) { + // Not supported on Android. +} + +Size2i DisplayServerAndroid::window_get_max_size(DisplayServer::WindowID p_window) const { + return Size2i(); +} + +void DisplayServerAndroid::window_set_min_size(const Size2i p_size, DisplayServer::WindowID p_window) { + // Not supported on Android. +} + +Size2i DisplayServerAndroid::window_get_min_size(DisplayServer::WindowID p_window) const { + return Size2i(); +} + +void DisplayServerAndroid::window_set_size(const Size2i p_size, DisplayServer::WindowID p_window) { + // Not supported on Android. +} + +Size2i DisplayServerAndroid::window_get_size(DisplayServer::WindowID p_window) const { + return OS_Android::get_singleton()->get_display_size(); +} + +Size2i DisplayServerAndroid::window_get_real_size(DisplayServer::WindowID p_window) const { + return OS_Android::get_singleton()->get_display_size(); +} + +void DisplayServerAndroid::window_set_mode(DisplayServer::WindowMode p_mode, DisplayServer::WindowID p_window) { + // Not supported on Android. +} + +DisplayServer::WindowMode DisplayServerAndroid::window_get_mode(DisplayServer::WindowID p_window) const { + return WINDOW_MODE_FULLSCREEN; +} + +bool DisplayServerAndroid::window_is_maximize_allowed(DisplayServer::WindowID p_window) const { + return false; +} + +void DisplayServerAndroid::window_set_flag(DisplayServer::WindowFlags p_flag, bool p_enabled, DisplayServer::WindowID p_window) { + // Not supported on Android. +} + +bool DisplayServerAndroid::window_get_flag(DisplayServer::WindowFlags p_flag, DisplayServer::WindowID p_window) const { + return false; +} + +void DisplayServerAndroid::window_request_attention(DisplayServer::WindowID p_window) { + // Not supported on Android. +} + +void DisplayServerAndroid::window_move_to_foreground(DisplayServer::WindowID p_window) { + // Not supported on Android. +} + +bool DisplayServerAndroid::window_can_draw(DisplayServer::WindowID p_window) const { + return true; +} + +bool DisplayServerAndroid::can_any_window_draw() const { + return true; +} + +void DisplayServerAndroid::alert(const String &p_alert, const String &p_title) { + GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); + ERR_FAIL_COND(!godot_java); + + godot_java->alert(p_alert, p_title); +} + +void DisplayServerAndroid::process_events() { + Input::get_singleton()->flush_accumulated_events(); +} + +Vector<String> DisplayServerAndroid::get_rendering_drivers_func() { + Vector<String> drivers; + +#ifdef OPENGL_ENABLED + drivers.push_back("opengl"); +#endif +#ifdef VULKAN_ENABLED + drivers.push_back("vulkan"); +#endif + + return drivers; +} + +DisplayServer *DisplayServerAndroid::create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { + DisplayServer *ds = memnew(DisplayServerAndroid(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); + if (r_error != OK) { + ds->alert("Your video card driver does not support any of the supported Vulkan versions.", "Unable to initialize Video driver"); + } + return ds; +} + +void DisplayServerAndroid::register_android_driver() { + register_create_function("android", create_func, get_rendering_drivers_func); +} + +void DisplayServerAndroid::reset_window() { +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + ANativeWindow *native_window = OS_Android::get_singleton()->get_native_window(); + ERR_FAIL_COND(!native_window); + + ERR_FAIL_COND(!context_vulkan); + context_vulkan->window_destroy(MAIN_WINDOW_ID); + + Size2i display_size = OS_Android::get_singleton()->get_display_size(); + if (context_vulkan->window_create(native_window, display_size.width, display_size.height) == -1) { + memdelete(context_vulkan); + context_vulkan = nullptr; + ERR_FAIL_MSG("Failed to reset Vulkan window."); + } + } +#endif +} + +DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { + rendering_driver = p_rendering_driver; + + // TODO: rendering_driver is broken, change when different drivers are supported again + rendering_driver = "vulkan"; + + keep_screen_on = GLOBAL_GET("display/window/energy_saving/keep_screen_on"); + + buttons_state = 0; + +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl") { + 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; + } + } +#endif + +#if defined(VULKAN_ENABLED) + context_vulkan = nullptr; + rendering_device_vulkan = nullptr; + + if (rendering_driver == "vulkan") { + ANativeWindow *native_window = OS_Android::get_singleton()->get_native_window(); + ERR_FAIL_COND(!native_window); + + context_vulkan = memnew(VulkanContextAndroid); + if (context_vulkan->initialize() != OK) { + memdelete(context_vulkan); + context_vulkan = nullptr; + ERR_FAIL_MSG("Failed to initialize Vulkan context"); + } + + Size2i display_size = OS_Android::get_singleton()->get_display_size(); + if (context_vulkan->window_create(native_window, display_size.width, display_size.height) == -1) { + memdelete(context_vulkan); + context_vulkan = nullptr; + ERR_FAIL_MSG("Failed to create Vulkan window."); + } + + rendering_device_vulkan = memnew(RenderingDeviceVulkan); + rendering_device_vulkan->initialize(context_vulkan); + + RendererCompositorRD::make_current(); + } +#endif + + Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + + r_error = OK; +} + +DisplayServerAndroid::~DisplayServerAndroid() { +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + if (rendering_device_vulkan) { + rendering_device_vulkan->finalize(); + memdelete(rendering_device_vulkan); + } + + if (context_vulkan) { + memdelete(context_vulkan); + } + } +#endif +} + +void DisplayServerAndroid::process_joy_event(DisplayServerAndroid::JoypadEvent p_event) { + switch (p_event.type) { + case JOY_EVENT_BUTTON: + Input::get_singleton()->joy_button(p_event.device, p_event.index, p_event.pressed); + break; + case JOY_EVENT_AXIS: + Input::JoyAxis value; + value.min = -1; + value.value = p_event.value; + Input::get_singleton()->joy_axis(p_event.device, p_event.index, value); + break; + case JOY_EVENT_HAT: + Input::get_singleton()->joy_hat(p_event.device, p_event.hat); + break; + default: + return; + } +} + +void DisplayServerAndroid::_set_key_modifier_state(Ref<InputEventWithModifiers> ev) { + ev->set_shift(shift_mem); + ev->set_alt(alt_mem); + ev->set_metakey(meta_mem); + ev->set_control(control_mem); +} + +void DisplayServerAndroid::process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed) { + static char32_t prev_wc = 0; + char32_t unicode = p_unicode_char; + if ((p_unicode_char & 0xfffffc00) == 0xd800) { + if (prev_wc != 0) { + ERR_PRINT("invalid utf16 surrogate input"); + } + prev_wc = unicode; + return; // Skip surrogate. + } else if ((unicode & 0xfffffc00) == 0xdc00) { + if (prev_wc == 0) { + ERR_PRINT("invalid utf16 surrogate input"); + return; // Skip invalid surrogate. + } + unicode = (prev_wc << 10UL) + unicode - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev_wc = 0; + } else { + prev_wc = 0; + } + + Ref<InputEventKey> ev; + ev.instance(); + int val = unicode; + int keycode = android_get_keysym(p_keycode); + int phy_keycode = android_get_keysym(p_scancode); + + if (keycode == KEY_SHIFT) { + shift_mem = p_pressed; + } + if (keycode == KEY_ALT) { + alt_mem = p_pressed; + } + if (keycode == KEY_CONTROL) { + control_mem = p_pressed; + } + if (keycode == KEY_META) { + meta_mem = p_pressed; + } + + ev->set_keycode(keycode); + ev->set_physical_keycode(phy_keycode); + ev->set_unicode(val); + ev->set_pressed(p_pressed); + + _set_key_modifier_state(ev); + + if (val == '\n') { + ev->set_keycode(KEY_ENTER); + } else if (val == 61448) { + ev->set_keycode(KEY_BACKSPACE); + ev->set_unicode(KEY_BACKSPACE); + } else if (val == 61453) { + ev->set_keycode(KEY_ENTER); + ev->set_unicode(KEY_ENTER); + } else if (p_keycode == 4) { + OS_Android::get_singleton()->main_loop_request_go_back(); + } + + Input::get_singleton()->accumulate_input_event(ev); +} + +void DisplayServerAndroid::process_touch(int p_event, int p_pointer, const Vector<DisplayServerAndroid::TouchPos> &p_points) { + switch (p_event) { + case AMOTION_EVENT_ACTION_DOWN: { //gesture begin + if (touch.size()) { + //end all if exist + for (int i = 0; i < touch.size(); i++) { + Ref<InputEventScreenTouch> ev; + ev.instance(); + ev->set_index(touch[i].id); + ev->set_pressed(false); + ev->set_position(touch[i].pos); + Input::get_singleton()->accumulate_input_event(ev); + } + } + + touch.resize(p_points.size()); + for (int i = 0; i < p_points.size(); i++) { + touch.write[i].id = p_points[i].id; + touch.write[i].pos = p_points[i].pos; + } + + //send touch + for (int i = 0; i < touch.size(); i++) { + Ref<InputEventScreenTouch> ev; + ev.instance(); + ev->set_index(touch[i].id); + ev->set_pressed(true); + ev->set_position(touch[i].pos); + Input::get_singleton()->accumulate_input_event(ev); + } + + } break; + case AMOTION_EVENT_ACTION_MOVE: { //motion + ERR_FAIL_COND(touch.size() != p_points.size()); + + for (int i = 0; i < touch.size(); i++) { + int idx = -1; + for (int j = 0; j < p_points.size(); j++) { + if (touch[i].id == p_points[j].id) { + idx = j; + break; + } + } + + ERR_CONTINUE(idx == -1); + + if (touch[i].pos == p_points[idx].pos) + continue; //no move unncesearily + + Ref<InputEventScreenDrag> ev; + ev.instance(); + ev->set_index(touch[i].id); + ev->set_position(p_points[idx].pos); + ev->set_relative(p_points[idx].pos - touch[i].pos); + Input::get_singleton()->accumulate_input_event(ev); + touch.write[i].pos = p_points[idx].pos; + } + + } break; + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_UP: { //release + if (touch.size()) { + //end all if exist + for (int i = 0; i < touch.size(); i++) { + Ref<InputEventScreenTouch> ev; + ev.instance(); + ev->set_index(touch[i].id); + ev->set_pressed(false); + ev->set_position(touch[i].pos); + Input::get_singleton()->accumulate_input_event(ev); + } + touch.clear(); + } + } break; + case AMOTION_EVENT_ACTION_POINTER_DOWN: { // add touch + for (int i = 0; i < p_points.size(); i++) { + if (p_points[i].id == p_pointer) { + TouchPos tp = p_points[i]; + touch.push_back(tp); + + Ref<InputEventScreenTouch> ev; + ev.instance(); + + ev->set_index(tp.id); + ev->set_pressed(true); + ev->set_position(tp.pos); + Input::get_singleton()->accumulate_input_event(ev); + + break; + } + } + } break; + case AMOTION_EVENT_ACTION_POINTER_UP: { // remove touch + for (int i = 0; i < touch.size(); i++) { + if (touch[i].id == p_pointer) { + Ref<InputEventScreenTouch> ev; + ev.instance(); + ev->set_index(touch[i].id); + ev->set_pressed(false); + ev->set_position(touch[i].pos); + Input::get_singleton()->accumulate_input_event(ev); + touch.remove(i); + + break; + } + } + } break; + } +} + +void DisplayServerAndroid::process_hover(int p_type, Point2 p_pos) { + // https://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER + switch (p_type) { + case AMOTION_EVENT_ACTION_HOVER_MOVE: // hover move + case AMOTION_EVENT_ACTION_HOVER_ENTER: // hover enter + case AMOTION_EVENT_ACTION_HOVER_EXIT: { // hover exit + Ref<InputEventMouseMotion> ev; + ev.instance(); + _set_key_modifier_state(ev); + ev->set_position(p_pos); + ev->set_global_position(p_pos); + ev->set_relative(p_pos - hover_prev_pos); + Input::get_singleton()->accumulate_input_event(ev); + hover_prev_pos = p_pos; + } break; + } +} + +void DisplayServerAndroid::process_mouse_event(int input_device, int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor, float event_horizontal_factor) { + int event_buttons_mask = _android_button_mask_to_godot_button_mask(event_android_buttons_mask); + switch (event_action) { + case AMOTION_EVENT_ACTION_BUTTON_PRESS: + case AMOTION_EVENT_ACTION_BUTTON_RELEASE: { + Ref<InputEventMouseButton> ev; + ev.instance(); + _set_key_modifier_state(ev); + if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { + ev->set_position(event_pos); + ev->set_global_position(event_pos); + } else { + ev->set_position(hover_prev_pos); + ev->set_global_position(hover_prev_pos); + } + ev->set_pressed(event_action == AMOTION_EVENT_ACTION_BUTTON_PRESS); + int changed_button_mask = buttons_state ^ event_buttons_mask; + + buttons_state = event_buttons_mask; + + ev->set_button_index(_button_index_from_mask(changed_button_mask)); + ev->set_button_mask(event_buttons_mask); + Input::get_singleton()->accumulate_input_event(ev); + } break; + + case AMOTION_EVENT_ACTION_MOVE: { + Ref<InputEventMouseMotion> ev; + ev.instance(); + _set_key_modifier_state(ev); + if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { + ev->set_position(event_pos); + ev->set_global_position(event_pos); + ev->set_relative(event_pos - hover_prev_pos); + hover_prev_pos = event_pos; + } else { + ev->set_position(hover_prev_pos); + ev->set_global_position(hover_prev_pos); + ev->set_relative(event_pos); + } + ev->set_button_mask(event_buttons_mask); + Input::get_singleton()->accumulate_input_event(ev); + } break; + case AMOTION_EVENT_ACTION_SCROLL: { + Ref<InputEventMouseButton> ev; + ev.instance(); + if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { + ev->set_position(event_pos); + ev->set_global_position(event_pos); + } else { + ev->set_position(hover_prev_pos); + ev->set_global_position(hover_prev_pos); + } + ev->set_pressed(true); + buttons_state = event_buttons_mask; + if (event_vertical_factor > 0) { + _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_UP, event_vertical_factor); + } else if (event_vertical_factor < 0) { + _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_DOWN, -event_vertical_factor); + } + + if (event_horizontal_factor > 0) { + _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_RIGHT, event_horizontal_factor); + } else if (event_horizontal_factor < 0) { + _wheel_button_click(event_buttons_mask, ev, BUTTON_WHEEL_LEFT, -event_horizontal_factor); + } + } break; + } +} + +void DisplayServerAndroid::_wheel_button_click(int event_buttons_mask, const Ref<InputEventMouseButton> &ev, int wheel_button, float factor) { + Ref<InputEventMouseButton> evd = ev->duplicate(); + _set_key_modifier_state(evd); + evd->set_button_index(wheel_button); + evd->set_button_mask(event_buttons_mask ^ (1 << (wheel_button - 1))); + evd->set_factor(factor); + Input::get_singleton()->accumulate_input_event(evd); + Ref<InputEventMouseButton> evdd = evd->duplicate(); + evdd->set_pressed(false); + evdd->set_button_mask(event_buttons_mask); + Input::get_singleton()->accumulate_input_event(evdd); +} + +void DisplayServerAndroid::process_double_tap(int event_android_button_mask, Point2 p_pos) { + int event_button_mask = _android_button_mask_to_godot_button_mask(event_android_button_mask); + Ref<InputEventMouseButton> ev; + ev.instance(); + _set_key_modifier_state(ev); + ev->set_position(p_pos); + ev->set_global_position(p_pos); + ev->set_pressed(event_button_mask != 0); + ev->set_button_index(_button_index_from_mask(event_button_mask)); + ev->set_button_mask(event_button_mask); + ev->set_doubleclick(true); + Input::get_singleton()->accumulate_input_event(ev); +} + +int DisplayServerAndroid::_button_index_from_mask(int button_mask) { + switch (button_mask) { + case BUTTON_MASK_LEFT: + return BUTTON_LEFT; + case BUTTON_MASK_RIGHT: + return BUTTON_RIGHT; + case BUTTON_MASK_MIDDLE: + return BUTTON_MIDDLE; + case BUTTON_MASK_XBUTTON1: + return BUTTON_XBUTTON1; + case BUTTON_MASK_XBUTTON2: + return BUTTON_XBUTTON2; + default: + return 0; + } +} + +void DisplayServerAndroid::process_scroll(Point2 p_pos) { + Ref<InputEventPanGesture> ev; + ev.instance(); + _set_key_modifier_state(ev); + ev->set_position(p_pos); + ev->set_delta(p_pos - scroll_prev_pos); + Input::get_singleton()->accumulate_input_event(ev); + scroll_prev_pos = p_pos; +} + +void DisplayServerAndroid::process_accelerometer(const Vector3 &p_accelerometer) { + Input::get_singleton()->set_accelerometer(p_accelerometer); +} + +void DisplayServerAndroid::process_gravity(const Vector3 &p_gravity) { + Input::get_singleton()->set_gravity(p_gravity); +} + +void DisplayServerAndroid::process_magnetometer(const Vector3 &p_magnetometer) { + Input::get_singleton()->set_magnetometer(p_magnetometer); +} + +void DisplayServerAndroid::process_gyroscope(const Vector3 &p_gyroscope) { + Input::get_singleton()->set_gyroscope(p_gyroscope); +} + +void DisplayServerAndroid::mouse_set_mode(MouseMode p_mode) { + if (mouse_mode == p_mode) { + return; + } + + if (p_mode == MouseMode::MOUSE_MODE_CAPTURED) { + OS_Android::get_singleton()->get_godot_java()->get_godot_view()->request_pointer_capture(); + } else { + OS_Android::get_singleton()->get_godot_java()->get_godot_view()->release_pointer_capture(); + } + + mouse_mode = p_mode; +} + +DisplayServer::MouseMode DisplayServerAndroid::mouse_get_mode() const { + return mouse_mode; +} + +Point2i DisplayServerAndroid::mouse_get_position() const { + return hover_prev_pos; +} + +int DisplayServerAndroid::mouse_get_button_state() const { + return buttons_state; +} + +int DisplayServerAndroid::_android_button_mask_to_godot_button_mask(int android_button_mask) { + int godot_button_mask = 0; + if (android_button_mask & AMOTION_EVENT_BUTTON_PRIMARY) { + godot_button_mask |= BUTTON_MASK_LEFT; + } + if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) { + godot_button_mask |= BUTTON_MASK_RIGHT; + } + if (android_button_mask & AMOTION_EVENT_BUTTON_TERTIARY) { + godot_button_mask |= BUTTON_MASK_MIDDLE; + } + if (android_button_mask & AMOTION_EVENT_BUTTON_BACK) { + godot_button_mask |= BUTTON_MASK_XBUTTON1; + } + if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) { + godot_button_mask |= BUTTON_MASK_XBUTTON2; + } + + return godot_button_mask; +} diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h new file mode 100644 index 0000000000..b9d1641656 --- /dev/null +++ b/platform/android/display_server_android.h @@ -0,0 +1,199 @@ +/*************************************************************************/ +/* display_server_android.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef DISPLAY_SERVER_ANDROID_H +#define DISPLAY_SERVER_ANDROID_H + +#include "servers/display_server.h" + +#if defined(VULKAN_ENABLED) +class VulkanContextAndroid; +class RenderingDeviceVulkan; +#endif + +class DisplayServerAndroid : public DisplayServer { +public: + struct TouchPos { + int id = 0; + Point2 pos; + }; + + enum { + JOY_EVENT_BUTTON = 0, + JOY_EVENT_AXIS = 1, + JOY_EVENT_HAT = 2 + }; + + struct JoypadEvent { + int device = 0; + int type = 0; + int index = 0; + bool pressed = false; + float value = 0; + int hat = 0; + }; + +private: + String rendering_driver; + + bool alt_mem = false; + bool shift_mem = false; + bool control_mem = false; + bool meta_mem = false; + + int buttons_state; + + MouseMode mouse_mode; + + bool keep_screen_on; + + Vector<TouchPos> touch; + Point2 hover_prev_pos; // needed to calculate the relative position on hover events + Point2 scroll_prev_pos; // needed to calculate the relative position on scroll events + +#if defined(VULKAN_ENABLED) + VulkanContextAndroid *context_vulkan; + RenderingDeviceVulkan *rendering_device_vulkan; +#endif + + ObjectID window_attached_instance_id; + + Callable window_event_callback; + Callable input_event_callback; + Callable input_text_callback; + + void _window_callback(const Callable &p_callable, const Variant &p_arg) const; + + static void _dispatch_input_events(const Ref<InputEvent> &p_event); + + void _set_key_modifier_state(Ref<InputEventWithModifiers> ev); + + static int _button_index_from_mask(int button_mask); + + static int _android_button_mask_to_godot_button_mask(int android_button_mask); + + void _wheel_button_click(int event_buttons_mask, const Ref<InputEventMouseButton> &ev, int wheel_button, float factor); + +public: + static DisplayServerAndroid *get_singleton(); + + virtual bool has_feature(Feature p_feature) const; + virtual String get_name() const; + + virtual void clipboard_set(const String &p_text); + virtual String clipboard_get() const; + + virtual void screen_set_keep_on(bool p_enable); + virtual bool screen_is_kept_on() const; + + virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW); + virtual ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + + virtual int get_screen_count() const; + virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + + virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1); + virtual void virtual_keyboard_hide(); + virtual int virtual_keyboard_get_height() const; + + virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); + virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); + virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); + virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); + virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); + + void send_window_event(WindowEvent p_event) const; + void send_input_event(const Ref<InputEvent> &p_event) const; + void send_input_text(const String &p_text) const; + + virtual Vector<WindowID> get_window_list() const; + virtual WindowID get_window_at_screen_position(const Point2i &p_position) const; + virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID); + virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID); + virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID); + virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID); + virtual void window_set_transient(WindowID p_window, WindowID p_parent); + virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID); + virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID); + virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID); + virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const; + virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID); + virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const; + virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID); + virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const; + virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID); + virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID); + virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const; + virtual bool can_any_window_draw() const; + + virtual void alert(const String &p_alert, const String &p_title); + + virtual void process_events(); + + void process_accelerometer(const Vector3 &p_accelerometer); + void process_gravity(const Vector3 &p_gravity); + void process_magnetometer(const Vector3 &p_magnetometer); + void process_gyroscope(const Vector3 &p_gyroscope); + void process_touch(int p_event, int p_pointer, const Vector<TouchPos> &p_points); + void process_hover(int p_type, Point2 p_pos); + void process_mouse_event(int input_device, int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor = 0, float event_horizontal_factor = 0); + void process_double_tap(int event_android_button_mask, Point2 p_pos); + void process_scroll(Point2 p_pos); + void process_joy_event(JoypadEvent p_event); + void process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed); + + void mouse_set_mode(MouseMode p_mode); + MouseMode mouse_get_mode() const; + + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + static Vector<String> get_rendering_drivers_func(); + static void register_android_driver(); + + void reset_window(); + + virtual Point2i mouse_get_position() const; + virtual int mouse_get_button_state() const; + + DisplayServerAndroid(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); + ~DisplayServerAndroid(); +}; + +#endif // DISPLAY_SERVER_ANDROID_H diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index eb461860ed..326e513261 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,20 +30,24 @@ #include "export.h" +#include "core/config/project_settings.h" #include "core/io/image_loader.h" #include "core/io/marshalls.h" #include "core/io/zip_io.h" #include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/os.h" -#include "core/project_settings.h" +#include "core/templates/safe_refcount.h" #include "core/version.h" #include "drivers/png/png_driver_common.h" #include "editor/editor_export.h" #include "editor/editor_log.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "main/splash.gen.h" +#include "platform/android/export/gradle_export_util.h" #include "platform/android/logo.gen.h" +#include "platform/android/plugin/godot_plugin_config.h" #include "platform/android/run_icon.gen.h" #include <string.h> @@ -194,18 +198,34 @@ static const char *android_perms[] = { "WRITE_SOCIAL_STREAM", "WRITE_SYNC_SETTINGS", "WRITE_USER_DICTIONARY", - NULL + nullptr }; +static const char *SPLASH_IMAGE_EXPORT_PATH = "res/drawable/splash.png"; +static const char *SPLASH_BG_COLOR_PATH = "res/drawable/splash_bg_color.png"; +static const char *SPLASH_CONFIG_PATH = "res://android/build/res/drawable/splash_drawable.xml"; + +const String SPLASH_CONFIG_XML_CONTENT = R"SPLASH(<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/splash_bg_color" /> + <item> + <bitmap + android:gravity="%s" + android:filter="%s" + android:src="@drawable/splash" /> + </item> +</layer-list> +)SPLASH"; + struct LauncherIcon { const char *export_path; - int dimensions; + int dimensions = 0; }; static const int icon_densities_count = 6; -static const char *launcher_icon_option = "launcher_icon/xxxhdpi_192x192"; -static const char *launcher_adaptive_icon_foreground_option = "launcher_adaptive_icon_foreground/xxxhdpi_432x432"; -static const char *launcher_adaptive_icon_background_option = "launcher_adaptive_icon_background/xxxhdpi_432x432"; +static const char *launcher_icon_option = "launcher_icons/main_192x192"; +static const char *launcher_adaptive_icon_foreground_option = "launcher_icons/adaptive_foreground_432x432"; +static const char *launcher_adaptive_icon_background_option = "launcher_icons/adaptive_background_432x432"; static const LauncherIcon launcher_icons[icon_densities_count] = { { "res/mipmap-xxxhdpi-v4/icon.png", 192 }, @@ -234,75 +254,96 @@ static const LauncherIcon launcher_adaptive_icon_backgrounds[icon_densities_coun { "res/mipmap/icon_background.png", 432 } }; -class EditorExportPlatformAndroid : public EditorExportPlatform { +static const int EXPORT_FORMAT_APK = 0; +static const int EXPORT_FORMAT_AAB = 1; +class EditorExportPlatformAndroid : public EditorExportPlatform { GDCLASS(EditorExportPlatformAndroid, EditorExportPlatform); Ref<ImageTexture> logo; Ref<ImageTexture> run_icon; struct Device { - String id; String name; String description; - int api_level; - bool usb; + int api_level = 0; }; struct APKExportData { - zipFile apk; - EditorProgress *ep; + EditorProgress *ep = nullptr; }; + Vector<PluginConfigAndroid> plugins; + String last_plugin_names; + uint64_t last_custom_build_time = 0; + SafeFlag plugins_changed; + Mutex plugins_lock; Vector<Device> devices; - volatile bool devices_changed; - Mutex *device_lock; - Thread *device_thread; - volatile bool quit_request; - - static void _device_poll_thread(void *ud) { + SafeFlag devices_changed; + Mutex device_lock; + Thread check_for_changes_thread; + SafeFlag quit_request; + static void _check_for_changes_poll_thread(void *ud) { EditorExportPlatformAndroid *ea = (EditorExportPlatformAndroid *)ud; - while (!ea->quit_request) { + while (!ea->quit_request.is_set()) { + // Check for plugins updates + { + // Nothing to do if we already know the plugins have changed. + if (!ea->plugins_changed.is_set()) { + Vector<PluginConfigAndroid> loaded_plugins = get_plugins(); - String adb = EditorSettings::get_singleton()->get("export/android/adb"); - if (FileAccess::exists(adb)) { + MutexLock lock(ea->plugins_lock); + + if (ea->plugins.size() != loaded_plugins.size()) { + ea->plugins_changed.set(); + } else { + for (int i = 0; i < ea->plugins.size(); i++) { + if (ea->plugins[i].name != loaded_plugins[i].name) { + ea->plugins_changed.set(); + break; + } + } + } + + if (ea->plugins_changed.is_set()) { + ea->plugins = loaded_plugins; + } + } + } + // Check for devices updates + String adb = get_adb_path(); + if (FileAccess::exists(adb)) { String devices; List<String> args; args.push_back("devices"); - args.push_back("-l"); int ec; - OS::get_singleton()->execute(adb, args, true, NULL, &devices, &ec); + OS::get_singleton()->execute(adb, args, &devices, &ec); Vector<String> ds = devices.split("\n"); Vector<String> ldevices; - Vector<bool> ldevices_usbconnection; for (int i = 1; i < ds.size(); i++) { - String d = ds[i]; - int dpos = d.find(" device "); - if (dpos == -1) + int dpos = d.find("device"); + if (dpos == -1) { continue; - ldevices_usbconnection.push_back(d.find(" usb:") != -1); + } d = d.substr(0, dpos).strip_edges(); ldevices.push_back(d); } - ea->device_lock->lock(); + MutexLock lock(ea->device_lock); bool different = false; if (ea->devices.size() != ldevices.size()) { - different = true; } else { - for (int i = 0; i < ea->devices.size(); i++) { - if (ea->devices[i].id != ldevices[i]) { different = true; break; @@ -311,14 +352,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } if (different) { - Vector<Device> ndevices; for (int i = 0; i < ldevices.size(); i++) { - Device d; d.id = ldevices[i]; - d.usb = ldevices_usbconnection[i]; for (int j = 0; j < ea->devices.size(); j++) { if (ea->devices[j].id == ldevices[i]) { d.description = ea->devices[j].description; @@ -337,15 +375,14 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { int ec2; String dp; - OS::get_singleton()->execute(adb, args, true, NULL, &dp, &ec2); + OS::get_singleton()->execute(adb, args, &dp, &ec2); Vector<String> props = dp.split("\n"); String vendor; String device; - d.description + "Device ID: " + d.id + "\n"; + d.description = "Device ID: " + d.id + "\n"; d.api_level = 0; for (int j = 0; j < props.size(); j++) { - // got information by `shell cat /system/build.prop` before and its format is "property=value" // it's now changed to use `shell getporp` because of permission issue with Android 8.0 and above // its format is "[property]: [value]" so changed it as like build.prop @@ -373,55 +410,47 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } else if (p.begins_with("ro.opengles.version=")) { uint32_t opengl = p.get_slice("=", 1).to_int(); d.description += "OpenGL: " + itos(opengl >> 16) + "." + itos((opengl >> 8) & 0xFF) + "." + itos((opengl)&0xFF) + "\n"; - } else if (p.begins_with("ro.boot.serialno=")) { - d.description += "Serial: " + p.get_slice("=", 1).strip_edges() + "\n"; } } - if (d.usb) { - d.description += "Connection: USB\n"; - } else { - d.description += "Connection: " + d.id + "\n"; - } - d.name = vendor + " " + device; - if (device == String()) continue; + if (device == String()) { + continue; + } } ndevices.push_back(d); } ea->devices = ndevices; - ea->devices_changed = true; + ea->devices_changed.set(); } - - ea->device_lock->unlock(); } - uint64_t sleep = OS::get_singleton()->get_power_state() == OS::POWERSTATE_ON_BATTERY ? 1000 : 100; + uint64_t sleep = 200; uint64_t wait = 3000000; uint64_t time = OS::get_singleton()->get_ticks_usec(); while (OS::get_singleton()->get_ticks_usec() - time < wait) { OS::get_singleton()->delay_usec(1000 * sleep); - if (ea->quit_request) + if (ea->quit_request.is_set()) { break; + } } } if (EditorSettings::get_singleton()->get("export/android/shutdown_adb_on_exit")) { - String adb = EditorSettings::get_singleton()->get("export/android/adb"); + String adb = get_adb_path(); if (!FileAccess::exists(adb)) { return; //adb not configured } List<String> args; args.push_back("kill-server"); - OS::get_singleton()->execute(adb, args, true); + OS::get_singleton()->execute(adb, args); }; } String get_project_name(const String &p_name) const { - String aname; if (p_name != "") { aname = p_name; @@ -437,7 +466,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } String get_package_name(const String &p_package) const { - String pname = p_package; String basename = ProjectSettings::get_singleton()->get("application/config/name"); basename = basename.to_lower(); @@ -445,7 +473,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { String name; bool first = true; for (int i = 0; i < basename.length(); i++) { - CharType c = basename[i]; + char32_t c = basename[i]; if (c >= '0' && c <= '9' && first) { continue; } @@ -454,16 +482,16 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { first = false; } } - if (name == "") + if (name == "") { name = "noname"; + } pname = pname.replace("$genname", name); return pname; } - bool is_package_name_valid(const String &p_package, String *r_error = NULL) const { - + bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const { String pname = p_package; if (pname.length() == 0) { @@ -476,7 +504,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { int segments = 0; bool first = true; for (int i = 0; i < pname.length(); i++) { - CharType c = pname[i]; + char32_t c = pname[i]; if (first && c == '.') { if (r_error) { *r_error = TTR("Package segments must be of non-zero length."); @@ -527,7 +555,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } static bool _should_compress_asset(const String &p_path, const Vector<uint8_t> &p_data) { - /* * By not compressing files with little or not benefit in doing so, * a performance gain is expected attime. Moreover, if the APK is @@ -552,7 +579,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { ".scn", // Binary scenes are usually already compressed ".stex", // Streamable textures are usually already compressed // Trailer for easier processing - NULL + nullptr }; for (const char **ext = unconditional_compress_ext; *ext; ++ext) { @@ -574,7 +601,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } static zip_fileinfo get_zip_fileinfo() { - OS::Time time = OS::get_singleton()->get_time(); OS::Date date = OS::get_singleton()->get_date(); @@ -582,7 +608,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { zipfi.tmz_date.tm_hour = time.hour; zipfi.tmz_date.tm_mday = date.day; zipfi.tmz_date.tm_min = time.min; - zipfi.tmz_date.tm_mon = date.month; + zipfi.tmz_date.tm_mon = date.month - 1; // tm_mon is zero indexed zipfi.tmz_date.tm_sec = time.sec; zipfi.tmz_date.tm_year = date.year; zipfi.dosDate = 0; @@ -601,16 +627,83 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { return abis; } + /// List the gdap files in the directory specified by the p_path parameter. + static Vector<String> list_gdap_files(const String &p_path) { + Vector<String> dir_files; + DirAccessRef da = DirAccess::open(p_path); + if (da) { + da->list_dir_begin(); + while (true) { + String file = da->get_next(); + if (file == "") { + break; + } + + if (da->current_is_dir() || da->current_is_hidden()) { + continue; + } + + if (file.ends_with(PluginConfigAndroid::PLUGIN_CONFIG_EXT)) { + dir_files.push_back(file); + } + } + da->list_dir_end(); + } + + return dir_files; + } + + static Vector<PluginConfigAndroid> get_plugins() { + Vector<PluginConfigAndroid> loaded_plugins; + + String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins"); + + // Add the prebuilt plugins + loaded_plugins.append_array(get_prebuilt_plugins(plugins_dir)); + + if (DirAccess::exists(plugins_dir)) { + Vector<String> plugins_filenames = list_gdap_files(plugins_dir); + + if (!plugins_filenames.is_empty()) { + Ref<ConfigFile> config_file = memnew(ConfigFile); + for (int i = 0; i < plugins_filenames.size(); i++) { + PluginConfigAndroid config = load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i])); + if (config.valid_config) { + loaded_plugins.push_back(config); + } else { + print_error("Invalid plugin config file " + plugins_filenames[i]); + } + } + } + } + + return loaded_plugins; + } + + static Vector<PluginConfigAndroid> get_enabled_plugins(const Ref<EditorExportPreset> &p_presets) { + Vector<PluginConfigAndroid> enabled_plugins; + Vector<PluginConfigAndroid> all_plugins = get_plugins(); + for (int i = 0; i < all_plugins.size(); i++) { + PluginConfigAndroid plugin = all_plugins[i]; + bool enabled = p_presets->get("plugins/" + plugin.name); + if (enabled) { + enabled_plugins.push_back(plugin); + } + } + + return enabled_plugins; + } + static Error store_in_apk(APKExportData *ed, const String &p_path, const Vector<uint8_t> &p_data, int compression_method = Z_DEFLATED) { zip_fileinfo zipfi = get_zip_fileinfo(); zipOpenNewFileInZip(ed->apk, p_path.utf8().get_data(), &zipfi, - NULL, + nullptr, 0, - NULL, + nullptr, 0, - NULL, + nullptr, compression_method, Z_DEFAULT_COMPRESSION); @@ -623,7 +716,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { static Error save_apk_so(void *p_userdata, const SharedObject &p_so) { if (!p_so.path.get_file().begins_with("lib")) { String err = "Android .so file names must start with \"lib\", but got: " + p_so.path; - ERR_PRINTS(err); + ERR_PRINT(err); return FAILED; } APKExportData *ed = (APKExportData *)p_userdata; @@ -644,29 +737,84 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { if (!exported) { String abis_string = String(" ").join(abis); String err = "Cannot determine ABI for library \"" + p_so.path + "\". One of the supported ABIs must be used as a tag: " + abis_string; - ERR_PRINTS(err); + ERR_PRINT(err); return FAILED; } return OK; } - static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) { + static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { APKExportData *ed = (APKExportData *)p_userdata; String dst_path = p_path.replace_first("res://", "assets/"); store_in_apk(ed, dst_path, p_data, _should_compress_asset(p_path, p_data) ? Z_DEFLATED : 0); - if (ed->ep->step("File: " + p_path, 3 + p_file * 100 / p_total)) { - return ERR_SKIP; - } return OK; } - static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) { + static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { return OK; } - void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) { + void _get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) { + const char **aperms = android_perms; + while (*aperms) { + bool enabled = p_preset->get("permissions/" + String(*aperms).to_lower()); + if (enabled) { + r_permissions.push_back("android.permission." + String(*aperms)); + } + aperms++; + } + PackedStringArray user_perms = p_preset->get("permissions/custom_permissions"); + for (int i = 0; i < user_perms.size(); i++) { + String user_perm = user_perms[i].strip_edges(); + if (!user_perm.is_empty()) { + r_permissions.push_back(user_perm); + } + } + if (p_give_internet) { + if (r_permissions.find("android.permission.INTERNET") == -1) { + r_permissions.push_back("android.permission.INTERNET"); + } + } + + int xr_mode_index = p_preset->get("xr_features/xr_mode"); + if (xr_mode_index == 1 /* XRMode.OVR */) { + int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required + if (hand_tracking_index > 0) { + if (r_permissions.find("com.oculus.permission.HAND_TRACKING") == -1) { + r_permissions.push_back("com.oculus.permission.HAND_TRACKING"); + } + } + } + } + + void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) { + print_verbose("Building temporary manifest.."); + String manifest_text = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\">\n"; + + manifest_text += _get_screen_sizes_tag(p_preset); + manifest_text += _get_gles_tag(); + + Vector<String> perms; + _get_permissions(p_preset, p_give_internet, perms); + for (int i = 0; i < perms.size(); i++) { + manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", perms.get(i)); + } + + manifest_text += _get_xr_features_tag(p_preset); + manifest_text += _get_instrumentation_tag(p_preset); + manifest_text += _get_application_tag(p_preset); + manifest_text += "</manifest>\n"; + String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); + print_verbose("Storing manifest into " + manifest_path + ": " + "\n" + manifest_text); + store_string_at_path(manifest_path, manifest_text); + } + + void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) { // Leaving the unused types commented because looking these constants up // again later would be annoying // const int CHUNK_AXML_FILE = 0x00080003; @@ -697,51 +845,26 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { int version_code = p_preset->get("version/code"); String package_name = p_preset->get("package/unique_name"); - int orientation = p_preset->get("screen/orientation"); + const int screen_orientation = _get_android_orientation_value(_get_screen_orientation()); - bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name") == "GLES3" && - !ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2"); bool screen_support_small = p_preset->get("screen/support_small"); bool screen_support_normal = p_preset->get("screen/support_normal"); bool screen_support_large = p_preset->get("screen/support_large"); bool screen_support_xlarge = p_preset->get("screen/support_xlarge"); int xr_mode_index = p_preset->get("xr_features/xr_mode"); + bool focus_awareness = p_preset->get("xr_features/focus_awareness"); Vector<String> perms; - - const char **aperms = android_perms; - while (*aperms) { - - bool enabled = p_preset->get("permissions/" + String(*aperms).to_lower()); - if (enabled) - perms.push_back("android.permission." + String(*aperms)); - aperms++; - } - - PoolStringArray user_perms = p_preset->get("permissions/custom_permissions"); - - for (int i = 0; i < user_perms.size(); i++) { - String user_perm = user_perms[i].strip_edges(); - if (!user_perm.empty()) { - perms.push_back(user_perm); - } - } - - if (p_give_internet) { - if (perms.find("android.permission.INTERNET") == -1) - perms.push_back("android.permission.INTERNET"); - } + // Write permissions into the perms variable. + _get_permissions(p_preset, p_give_internet, perms); while (ofs < (uint32_t)p_manifest.size()) { - uint32_t chunk = decode_uint32(&p_manifest[ofs]); uint32_t size = decode_uint32(&p_manifest[ofs + 4]); switch (chunk) { - case CHUNK_STRINGS: { - int iofs = ofs + 8; string_count = decode_uint32(&p_manifest[iofs]); @@ -762,17 +885,15 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { string_table_begins = st_offset; for (uint32_t i = 0; i < string_count; i++) { - uint32_t string_at = decode_uint32(&p_manifest[st_offset + i * 4]); string_at += st_offset + string_count * 4; ERR_FAIL_COND_MSG(string_flags & UTF8_FLAG, "Unimplemented, can't read UTF-8 string table."); if (string_flags & UTF8_FLAG) { - } else { uint32_t len = decode_uint16(&p_manifest[string_at]); - Vector<CharType> ucstring; + Vector<char32_t> ucstring; ucstring.resize(len + 1); for (uint32_t j = 0; j < len; j++) { uint16_t c = decode_uint16(&p_manifest[string_at + 2 + 2 * j]); @@ -792,13 +913,13 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } break; case CHUNK_XML_START_TAG: { - int iofs = ofs + 8; uint32_t name = decode_uint32(&p_manifest[iofs + 12]); String tname = string_table[name]; uint32_t attrcount = decode_uint32(&p_manifest[iofs + 20]); iofs += 28; + bool is_focus_aware_metadata = false; for (uint32_t i = 0; i < attrcount; i++) { uint32_t attr_nspace = decode_uint32(&p_manifest[iofs]); @@ -822,8 +943,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { if (tname == "manifest" && attrname == "versionName") { if (attr_value == 0xFFFFFFFF) { WARN_PRINT("Version name in a resource, should be plain text"); - } else + } else { string_table.write[attr_value] = version_name; + } } if (tname == "instrumentation" && attrname == "targetPackage") { @@ -831,35 +953,24 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } if (tname == "activity" && attrname == "screenOrientation") { - - encode_uint32(orientation == 0 ? 0 : 1, &p_manifest.write[iofs + 16]); + encode_uint32(screen_orientation, &p_manifest.write[iofs + 16]); } if (tname == "supports-screens") { - if (attrname == "smallScreens") { - encode_uint32(screen_support_small ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); } else if (attrname == "normalScreens") { - encode_uint32(screen_support_normal ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); } else if (attrname == "largeScreens") { - encode_uint32(screen_support_large ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); } else if (attrname == "xlargeScreens") { - encode_uint32(screen_support_xlarge ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); } } - if (tname == "uses-feature" && attrname == "glEsVersion") { - - encode_uint32(min_gles3 ? 0x00030000 : 0x00020000, &p_manifest.write[iofs + 16]); - } - // FIXME: `attr_value != 0xFFFFFFFF` below added as a stopgap measure for GH-32553, // but the issue should be debugged further and properly addressed. if (tname == "meta-data" && attrname == "name" && value == "xr_mode_metadata_name") { @@ -876,6 +987,12 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } } + if (tname == "meta-data" && attrname == "value" && is_focus_aware_metadata) { + // Update the focus awareness meta-data value + encode_uint32(xr_mode_index == /* XRMode.OVR */ 1 && focus_awareness ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); + } + + is_focus_aware_metadata = tname == "meta-data" && attrname == "name" && value == "com.oculus.vr.focusaware"; iofs += 20; } @@ -906,10 +1023,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { feature_names.push_back("oculus.software.handtracking"); feature_required_list.push_back(hand_tracking_index == 2); feature_versions.push_back(-1); // no version attribute should be added. - - if (perms.find("oculus.permission.handtracking") == -1) { - perms.push_back("oculus.permission.handtracking"); - } } } @@ -1055,7 +1168,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } } if (tname == "manifest") { - // save manifest ending so we can restore it Vector<uint8_t> manifest_end; uint32_t manifest_cur_size = p_manifest.size(); @@ -1141,13 +1253,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { ret.resize(string_table_begins + string_table.size() * 4); for (uint32_t i = 0; i < string_table_begins; i++) { - ret.write[i] = p_manifest[i]; } ofs = 0; for (int i = 0; i < string_table.size(); i++) { - encode_uint32(ofs, &ret.write[string_table_begins + i * 4]); ofs += string_table[i].length() * 2 + 2 + 2; } @@ -1156,7 +1266,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { string_data_offset = ret.size() - ofs; uint8_t *chars = &ret.write[string_data_offset]; for (int i = 0; i < string_table.size(); i++) { - String s = string_table[i]; encode_uint16(s.length(), chars); chars += 2; @@ -1173,18 +1282,21 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } //pad - while (ret.size() % 4) + while (ret.size() % 4) { ret.push_back(0); + } uint32_t new_stable_end = ret.size(); uint32_t extra = (p_manifest.size() - string_table_ends); ret.resize(new_stable_end + extra); - for (uint32_t i = 0; i < extra; i++) + for (uint32_t i = 0; i < extra; i++) { ret.write[new_stable_end + i] = p_manifest[string_table_ends + i]; + } - while (ret.size() % 4) + while (ret.size() % 4) { ret.push_back(0); + } encode_uint32(ret.size(), &ret.write[4]); //update new file size encode_uint32(new_stable_end - 8, &ret.write[12]); //update new string table size @@ -1195,19 +1307,36 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } static String _parse_string(const uint8_t *p_bytes, bool p_utf8) { - uint32_t offset = 0; - uint32_t len = decode_uint16(&p_bytes[offset]); + uint32_t len = 0; if (p_utf8) { - //don't know how to read extended utf8, this will have to be for now - len >>= 8; + uint8_t byte = p_bytes[offset]; + if (byte & 0x80) { + offset += 2; + } else { + offset += 1; + } + byte = p_bytes[offset]; + offset++; + if (byte & 0x80) { + len = byte & 0x7F; + len = (len << 8) + p_bytes[offset]; + offset++; + } else { + len = byte; + } + } else { + len = decode_uint16(&p_bytes[offset]); + offset += 2; + if (len & 0x8000) { + len &= 0x7FFF; + len = (len << 16) + decode_uint16(&p_bytes[offset]); + offset += 2; + } } - offset += 2; - //printf("len %i, unicode: %i\n",len,int(p_utf8)); if (p_utf8) { - Vector<uint8_t> str8; str8.resize(len + 1); for (uint32_t i = 0; i < len; i++) { @@ -1218,24 +1347,24 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { str.parse_utf8((const char *)str8.ptr()); return str; } else { - String str; for (uint32_t i = 0; i < len; i++) { - CharType c = decode_uint16(&p_bytes[offset + i * 2]); - if (c == 0) + char32_t c = decode_uint16(&p_bytes[offset + i * 2]); + if (c == 0) { break; + } str += String::chr(c); } return str; } } - void _fix_resources(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest) { + void _fix_resources(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &r_manifest) { const int UTF8_FLAG = 0x00000100; - uint32_t string_block_len = decode_uint32(&p_manifest[16]); - uint32_t string_count = decode_uint32(&p_manifest[20]); - uint32_t string_flags = decode_uint32(&p_manifest[28]); + uint32_t string_block_len = decode_uint32(&r_manifest[16]); + uint32_t string_count = decode_uint32(&r_manifest[20]); + uint32_t string_flags = decode_uint32(&r_manifest[28]); const uint32_t string_table_begins = 40; Vector<String> string_table; @@ -1243,21 +1372,18 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { String package_name = p_preset->get("package/name"); for (uint32_t i = 0; i < string_count; i++) { - - uint32_t offset = decode_uint32(&p_manifest[string_table_begins + i * 4]); + uint32_t offset = decode_uint32(&r_manifest[string_table_begins + i * 4]); offset += string_table_begins + string_count * 4; - String str = _parse_string(&p_manifest[offset], string_flags & UTF8_FLAG); + String str = _parse_string(&r_manifest[offset], string_flags & UTF8_FLAG); if (str.begins_with("godot-project-name")) { - if (str == "godot-project-name") { //project name str = get_project_name(package_name); } else { - - String lang = str.substr(str.find_last("-") + 1, str.length()).replace("-", "_"); + String lang = str.substr(str.rfind("-") + 1, str.length()).replace("-", "_"); String prop = "application/config/name_" + lang; if (ProjectSettings::get_singleton()->has_setting(prop)) { str = ProjectSettings::get_singleton()->get(prop); @@ -1275,13 +1401,11 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { ret.resize(string_table_begins + string_table.size() * 4); for (uint32_t i = 0; i < string_table_begins; i++) { - - ret.write[i] = p_manifest[i]; + ret.write[i] = r_manifest[i]; } int ofs = 0; for (int i = 0; i < string_table.size(); i++) { - encode_uint32(ofs, &ret.write[string_table_begins + i * 4]); ofs += string_table[i].length() * 2 + 2 + 2; } @@ -1289,7 +1413,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { ret.resize(ret.size() + ofs); uint8_t *chars = &ret.write[ret.size() - ofs]; for (int i = 0; i < string_table.size(); i++) { - String s = string_table[i]; encode_uint16(s.length(), chars); chars += 2; @@ -1302,8 +1425,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } //pad - while (ret.size() % 4) + while (ret.size() % 4) { ret.push_back(0); + } //change flags to not use utf8 encode_uint32(string_flags & ~0x100, &ret.write[28]); @@ -1312,35 +1436,183 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { //append the rest... int rest_from = 12 + string_block_len; int rest_to = ret.size(); - int rest_len = (p_manifest.size() - rest_from); - ret.resize(ret.size() + (p_manifest.size() - rest_from)); + int rest_len = (r_manifest.size() - rest_from); + ret.resize(ret.size() + (r_manifest.size() - rest_from)); for (int i = 0; i < rest_len; i++) { - ret.write[rest_to + i] = p_manifest[rest_from + i]; + ret.write[rest_to + i] = r_manifest[rest_from + i]; } //finally update the size encode_uint32(ret.size(), &ret.write[4]); - p_manifest = ret; + r_manifest = ret; //printf("end\n"); } - void _process_launcher_icons(const String &p_processing_file_name, const Ref<Image> &p_source_image, const LauncherIcon p_icon, Vector<uint8_t> &p_data) { - if (p_processing_file_name == p_icon.export_path) { - Ref<Image> working_image = p_source_image; + void _load_image_data(const Ref<Image> &p_splash_image, Vector<uint8_t> &p_data) { + Vector<uint8_t> png_buffer; + Error err = PNGDriverCommon::image_to_png(p_splash_image, png_buffer); + if (err == OK) { + p_data.resize(png_buffer.size()); + memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size()); + } else { + String err_str = String("Failed to convert splash image to png."); + WARN_PRINT(err_str.utf8().get_data()); + } + } + + void _process_launcher_icons(const String &p_file_name, const Ref<Image> &p_source_image, int dimension, Vector<uint8_t> &p_data) { + Ref<Image> working_image = p_source_image; + + if (p_source_image->get_width() != dimension || p_source_image->get_height() != dimension) { + working_image = p_source_image->duplicate(); + working_image->resize(dimension, dimension, Image::Interpolation::INTERPOLATE_LANCZOS); + } + + Vector<uint8_t> png_buffer; + Error err = PNGDriverCommon::image_to_png(working_image, png_buffer); + if (err == OK) { + p_data.resize(png_buffer.size()); + memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size()); + } else { + String err_str = String("Failed to convert resized icon (") + p_file_name + ") to png."; + WARN_PRINT(err_str.utf8().get_data()); + } + } - if (p_source_image->get_width() != p_icon.dimensions || p_source_image->get_height() != p_icon.dimensions) { - working_image = p_source_image->duplicate(); - working_image->resize(p_icon.dimensions, p_icon.dimensions, Image::Interpolation::INTERPOLATE_LANCZOS); + String load_splash_refs(Ref<Image> &splash_image, Ref<Image> &splash_bg_color_image) { + bool scale_splash = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize"); + bool apply_filter = ProjectSettings::get_singleton()->get("application/boot_splash/use_filter"); + String project_splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image"); + + if (!project_splash_path.is_empty()) { + splash_image.instance(); + print_verbose("Loading splash image: " + project_splash_path); + const Error err = ImageLoader::load_image(project_splash_path, splash_image); + if (err) { + if (OS::get_singleton()->is_stdout_verbose()) { + print_error("- unable to load splash image from " + project_splash_path + " (" + itos(err) + ")"); + } + splash_image.unref(); } + } - PoolVector<uint8_t> png_buffer; - Error err = PNGDriverCommon::image_to_png(working_image, png_buffer); - if (err == OK) { - p_data.resize(png_buffer.size()); - memcpy(p_data.ptrw(), png_buffer.read().ptr(), p_data.size()); - } else { - String err_str = String("Failed to convert resized icon (") + p_processing_file_name + ") to png."; - WARN_PRINT(err_str.utf8().get_data()); + if (splash_image.is_null()) { + // Use the default + print_verbose("Using default splash image."); + splash_image = Ref<Image>(memnew(Image(boot_splash_png))); + } + + // Setup the splash bg color + bool bg_color_valid; + Color bg_color = ProjectSettings::get_singleton()->get("application/boot_splash/bg_color", &bg_color_valid); + if (!bg_color_valid) { + bg_color = boot_splash_bg_color; + } + + print_verbose("Creating splash background color image."); + splash_bg_color_image.instance(); + splash_bg_color_image->create(splash_image->get_width(), splash_image->get_height(), false, splash_image->get_format()); + splash_bg_color_image->fill(bg_color); + + String gravity = scale_splash ? "fill" : "center"; + String processed_splash_config_xml = vformat(SPLASH_CONFIG_XML_CONTENT, gravity, bool_to_string(apply_filter)); + return processed_splash_config_xml; + } + + void load_icon_refs(const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background) { + String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon"); + + icon.instance(); + foreground.instance(); + background.instance(); + + // Regular icon: user selection -> project icon -> default. + String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges(); + print_verbose("Loading regular icon from " + path); + if (path.is_empty() || ImageLoader::load_image(path, icon) != OK) { + print_verbose("- falling back to project icon: " + project_icon_path); + ImageLoader::load_image(project_icon_path, icon); + } + + // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default). + path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges(); + print_verbose("Loading adaptive foreground icon from " + path); + if (path.is_empty() || ImageLoader::load_image(path, foreground) != OK) { + print_verbose("- falling back to using the regular icon"); + foreground = icon; + } + + // Adaptive background: user selection -> default. + path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges(); + if (!path.is_empty()) { + print_verbose("Loading adaptive background icon from " + path); + ImageLoader::load_image(path, background); + } + } + + void store_image(const LauncherIcon launcher_icon, const Vector<uint8_t> &data) { + store_image(launcher_icon.export_path, data); + } + + void store_image(const String &export_path, const Vector<uint8_t> &data) { + String img_path = export_path.insert(0, "res://android/build/"); + store_file_at_path(img_path, data); + } + + void _copy_icons_to_gradle_project(const Ref<EditorExportPreset> &p_preset, + const String &processed_splash_config_xml, + const Ref<Image> &splash_image, + const Ref<Image> &splash_bg_color_image, + const Ref<Image> &main_image, + const Ref<Image> &foreground, + const Ref<Image> &background) { + // Store the splash configuration + if (!processed_splash_config_xml.is_empty()) { + print_verbose("Storing processed splash configuration: " + String("\n") + processed_splash_config_xml); + store_string_at_path(SPLASH_CONFIG_PATH, processed_splash_config_xml); + } + + // Store the splash image + if (splash_image.is_valid() && !splash_image->is_empty()) { + print_verbose("Storing splash image in " + String(SPLASH_IMAGE_EXPORT_PATH)); + Vector<uint8_t> data; + _load_image_data(splash_image, data); + store_image(SPLASH_IMAGE_EXPORT_PATH, data); + } + + // Store the splash bg color image + if (splash_bg_color_image.is_valid() && !splash_bg_color_image->is_empty()) { + print_verbose("Storing splash background image in " + String(SPLASH_BG_COLOR_PATH)); + Vector<uint8_t> data; + _load_image_data(splash_bg_color_image, data); + store_image(SPLASH_BG_COLOR_PATH, data); + } + + // Prepare images to be resized for the icons. If some image ends up being uninitialized, + // the default image from the export template will be used. + + for (int i = 0; i < icon_densities_count; ++i) { + if (main_image.is_valid() && !main_image->is_empty()) { + print_verbose("Processing launcher icon for dimension " + itos(launcher_icons[i].dimensions) + " into " + launcher_icons[i].export_path); + Vector<uint8_t> data; + _process_launcher_icons(launcher_icons[i].export_path, main_image, launcher_icons[i].dimensions, data); + store_image(launcher_icons[i], data); + } + + if (foreground.is_valid() && !foreground->is_empty()) { + print_verbose("Processing launcher adaptive icon foreground for dimension " + itos(launcher_adaptive_icon_foregrounds[i].dimensions) + " into " + launcher_adaptive_icon_foregrounds[i].export_path); + Vector<uint8_t> data; + _process_launcher_icons(launcher_adaptive_icon_foregrounds[i].export_path, foreground, + launcher_adaptive_icon_foregrounds[i].dimensions, data); + store_image(launcher_adaptive_icon_foregrounds[i], data); + } + + if (background.is_valid() && !background->is_empty()) { + print_verbose("Processing launcher adaptive icon background for dimension " + itos(launcher_adaptive_icon_backgrounds[i].dimensions) + " into " + launcher_adaptive_icon_backgrounds[i].export_path); + Vector<uint8_t> data; + _process_launcher_icons(launcher_adaptive_icon_backgrounds[i].export_path, background, + launcher_adaptive_icon_backgrounds[i].dimensions, data); + store_image(launcher_adaptive_icon_backgrounds[i], data); } } } @@ -1358,19 +1630,17 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } public: - typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total); + typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); public: - virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) { - - String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name"); + virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override { + String driver = ProjectSettings::get_singleton()->get("rendering/driver/driver_name"); if (driver == "GLES2") { r_features->push_back("etc"); - } else if (driver == "GLES3") { + } + // FIXME: Review what texture formats are used for Vulkan. + if (driver == "Vulkan") { r_features->push_back("etc2"); - if (ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2")) { - r_features->push_back("etc"); - } } Vector<String> abis = get_enabled_abis(p_preset); @@ -1379,108 +1649,122 @@ public: } } - virtual void get_export_options(List<ExportOption> *r_options) { - - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/32_bits_framebuffer"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/xr_mode", PROPERTY_HINT_ENUM, "Regular,Oculus Mobile VR"), 0)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/degrees_of_freedom", PROPERTY_HINT_ENUM, "None,3DOF and 6DOF,6DOF"), 0)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking", PROPERTY_HINT_ENUM, "None,Optional,Required"), 0)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), false)); + virtual void get_export_options(List<ExportOption> *r_options) override { 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, "command_line/extra_args"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "custom_template/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), EXPORT_FORMAT_APK)); + + Vector<PluginConfigAndroid> plugins_configs = get_plugins(); + for (int i = 0; i < plugins_configs.size(); i++) { + print_verbose("Found Android plugin " + plugins_configs[i].name); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + plugins_configs[i].name), false)); + } + plugins_changed.clear(); + + Vector<String> abis = get_abis(); + for (int i = 0; i < abis.size(); ++i) { + String abi = abis[i]; + bool is_default = (abi == "armeabi-v7a" || abi == "arm64-v8a"); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "architectures/" + abi), is_default)); + } + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_user"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/signed"), true)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_icon_option, PROPERTY_HINT_FILE, "*.png"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_foreground_option, PROPERTY_HINT_FILE, "*.png"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_background_option, PROPERTY_HINT_FILE, "*.png"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/32_bits_framebuffer"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/opengl_debug"), false)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/xr_mode", PROPERTY_HINT_ENUM, "Regular,Oculus Mobile VR"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/degrees_of_freedom", PROPERTY_HINT_ENUM, "None,3DOF and 6DOF,6DOF"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "xr_features/hand_tracking", PROPERTY_HINT_ENUM, "None,Optional,Required"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "xr_features/focus_awareness"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/immersive_mode"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "screen/orientation", PROPERTY_HINT_ENUM, "Landscape,Portrait"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_small"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_normal"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_large"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_xlarge"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/opengl_debug"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_icon_option, PROPERTY_HINT_FILE, "*.png"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_foreground_option, PROPERTY_HINT_FILE, "*.png"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_background_option, PROPERTY_HINT_FILE, "*.png"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_user"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "apk_expansion/enable"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "apk_expansion/SALT"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "apk_expansion/public_key", PROPERTY_HINT_MULTILINE_TEXT), "")); - Vector<String> abis = get_abis(); - for (int i = 0; i < abis.size(); ++i) { - String abi = abis[i]; - bool is_default = (abi == "armeabi-v7a" || abi == "arm64-v8a"); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "architectures/" + abi), is_default)); - } - - r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "permissions/custom_permissions"), PoolStringArray())); + r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "permissions/custom_permissions"), PackedStringArray())); const char **perms = android_perms; while (*perms) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "permissions/" + String(*perms).to_lower()), false)); perms++; } } - virtual String get_name() const { + virtual String get_name() const override { return "Android"; } - virtual String get_os_name() const { + virtual String get_os_name() const override { return "Android"; } - virtual Ref<Texture> get_logo() const { + virtual Ref<Texture2D> get_logo() const override { return logo; } - virtual bool poll_export() { + virtual bool should_update_export_options() override { + bool export_options_changed = plugins_changed.is_set(); + if (export_options_changed) { + // don't clear unless we're reporting true, to avoid race + plugins_changed.clear(); + } + return export_options_changed; + } - bool dc = devices_changed; + virtual bool poll_export() override { + bool dc = devices_changed.is_set(); if (dc) { // don't clear unless we're reporting true, to avoid race - devices_changed = false; + devices_changed.clear(); } return dc; } - virtual int get_options_count() const { - - device_lock->lock(); - int dc = devices.size(); - device_lock->unlock(); - - return dc; + virtual int get_options_count() const override { + MutexLock lock(device_lock); + return devices.size(); } - virtual String get_options_tooltip() const { - + virtual String get_options_tooltip() const override { return TTR("Select device from the list"); } - virtual String get_option_label(int p_index) const { - + virtual String get_option_label(int p_index) const override { ERR_FAIL_INDEX_V(p_index, devices.size(), ""); - device_lock->lock(); - String s = devices[p_index].name; - device_lock->unlock(); - return s; + MutexLock lock(device_lock); + return devices[p_index].name; } - virtual String get_option_tooltip(int p_index) const { - + virtual String get_option_tooltip(int p_index) const override { ERR_FAIL_INDEX_V(p_index, devices.size(), ""); - device_lock->lock(); + MutexLock lock(device_lock); String s = devices[p_index].description; if (devices.size() == 1) { // Tooltip will be: @@ -1488,50 +1772,47 @@ public: // Description s = devices[p_index].name + "\n\n" + s; } - device_lock->unlock(); return s; } - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { - + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override { ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); - device_lock->lock(); - - EditorProgress ep("run", "Running on " + devices[p_device].name, 3); - String adb = EditorSettings::get_singleton()->get("export/android/adb"); - if (adb == "") { - - EditorNode::add_io_error("ADB executable not configured in settings, can't run."); - device_lock->unlock(); + String can_export_error; + bool can_export_missing_templates; + if (!can_export(p_preset, can_export_error, can_export_missing_templates)) { + EditorNode::add_io_error(can_export_error); return ERR_UNCONFIGURED; } + MutexLock lock(device_lock); + + EditorProgress ep("run", "Running on " + devices[p_device].name, 3); + + String adb = get_adb_path(); + // Export_temp APK. - if (ep.step("Exporting APK", 0)) { - device_lock->unlock(); + if (ep.step("Exporting APK...", 0)) { return ERR_SKIP; } const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); - const bool use_reverse = devices[p_device].api_level >= 21 && devices[p_device].usb; - // Note: Reverse can still fail if device is connected by both usb and network - // Ideally we'd know for sure whether adb reverse would work before we build the APK + const bool use_reverse = devices[p_device].api_level >= 21; - if (use_reverse) + if (use_reverse) { p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST; + } String tmp_export_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport.apk"); #define CLEANUP_AND_RETURN(m_err) \ { \ DirAccess::remove_file_or_error(tmp_export_path); \ - device_lock->unlock(); \ return m_err; \ } // Export to temporary APK before sending to device. - Error err = export_project(p_preset, true, tmp_export_path, p_debug_flags); + Error err = export_project_helper(p_preset, true, tmp_export_path, EXPORT_FORMAT_APK, true, p_debug_flags); if (err != OK) { CLEANUP_AND_RETURN(err); @@ -1556,11 +1837,11 @@ public: args.push_back("uninstall"); args.push_back(get_package_name(package_name)); - err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv); + err = OS::get_singleton()->execute(adb, args, nullptr, &rv); } print_line("Installing to device (please wait...): " + devices[p_device].name); - if (ep.step("Installing to device (please wait...)", 2)) { + if (ep.step("Installing to device, please wait...", 2)) { CLEANUP_AND_RETURN(ERR_SKIP); } @@ -1571,7 +1852,7 @@ public: args.push_back("-r"); args.push_back(tmp_export_path); - err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv); + err = OS::get_singleton()->execute(adb, args, nullptr, &rv); if (err || rv != 0) { EditorNode::add_io_error("Could not install to device."); CLEANUP_AND_RETURN(ERR_CANT_CREATE); @@ -1579,7 +1860,6 @@ public: if (use_remote) { if (use_reverse) { - static const char *const msg = "--- Device API >= 21; debugging over USB ---"; EditorNode::get_singleton()->get_log()->add_message(msg, EditorLog::MSG_TYPE_EDITOR); print_line(String(msg).to_upper()); @@ -1589,10 +1869,9 @@ public: args.push_back(devices[p_device].id); args.push_back("reverse"); args.push_back("--remove-all"); - OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv); + OS::get_singleton()->execute(adb, args, nullptr, &rv); if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) { - int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); args.clear(); args.push_back("-s"); @@ -1601,12 +1880,11 @@ public: args.push_back("tcp:" + itos(dbg_port)); args.push_back("tcp:" + itos(dbg_port)); - OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv); + OS::get_singleton()->execute(adb, args, nullptr, &rv); print_line("Reverse result: " + itos(rv)); } if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) { - int fs_port = EditorSettings::get_singleton()->get("filesystem/file_server/port"); args.clear(); @@ -1616,18 +1894,17 @@ public: args.push_back("tcp:" + itos(fs_port)); args.push_back("tcp:" + itos(fs_port)); - err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv); + err = OS::get_singleton()->execute(adb, args, nullptr, &rv); print_line("Reverse result2: " + itos(rv)); } } else { - - static const char *const msg = "--- Device API < 21 or no USB connection; debugging over Wi-Fi ---"; + static const char *const msg = "--- Device API < 21; debugging over Wi-Fi ---"; EditorNode::get_singleton()->get_log()->add_message(msg, EditorLog::MSG_TYPE_EDITOR); print_line(String(msg).to_upper()); } } - if (ep.step("Running on Device...", 3)) { + if (ep.step("Running on device...", 3)) { CLEANUP_AND_RETURN(ERR_SKIP); } args.clear(); @@ -1645,7 +1922,7 @@ public: args.push_back("-n"); args.push_back(get_package_name(package_name) + "/com.godot.game.GodotApp"); - err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv); + err = OS::get_singleton()->execute(adb, args, nullptr, &rv); if (err || rv != 0) { EditorNode::add_io_error("Could not execute on device."); CLEANUP_AND_RETURN(ERR_CANT_CREATE); @@ -1655,62 +1932,110 @@ public: #undef CLEANUP_AND_RETURN } - virtual Ref<Texture> get_run_icon() const { + virtual Ref<Texture2D> get_run_icon() const override { return run_icon; } - virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { + static String get_adb_path() { + String exe_ext = ""; + if (OS::get_singleton()->get_name() == "Windows") { + exe_ext = ".exe"; + } + String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path"); + return sdk_path.plus_file("platform-tools/adb" + exe_ext); + } + + static String get_apksigner_path() { + String exe_ext = ""; + if (OS::get_singleton()->get_name() == "Windows") { + exe_ext = ".bat"; + } + String apksigner_command_name = "apksigner" + exe_ext; + String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path"); + String apksigner_path = ""; + + Error errn; + String build_tools_dir = sdk_path.plus_file("build-tools"); + DirAccessRef da = DirAccess::open(build_tools_dir, &errn); + if (errn != OK) { + print_error("Unable to open Android 'build-tools' directory."); + return apksigner_path; + } + // There are additional versions directories we need to go through. + da->list_dir_begin(); + String sub_dir = da->get_next(); + while (!sub_dir.is_empty()) { + if (!sub_dir.begins_with(".") && da->current_is_dir()) { + // Check if the tool is here. + String tool_path = build_tools_dir.plus_file(sub_dir).plus_file(apksigner_command_name); + if (FileAccess::exists(tool_path)) { + apksigner_path = tool_path; + break; + } + } + sub_dir = da->get_next(); + } + da->list_dir_end(); + + if (apksigner_path.is_empty()) { + EditorNode::get_singleton()->show_warning(TTR("Unable to find the 'apksigner' tool.")); + } + + return apksigner_path; + } + + virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override { String err; bool valid = false; // Look for export templates (first official, and if defined custom templates). if (!bool(p_preset->get("custom_template/use_custom_build"))) { - bool dvalid = exists_export_template("android_debug.apk", &err); - bool rvalid = exists_export_template("android_release.apk", &err); + String template_err; + bool dvalid = false; + bool rvalid = false; + bool has_export_templates = false; if (p_preset->get("custom_template/debug") != "") { dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); if (!dvalid) { - err += TTR("Custom debug template not found.") + "\n"; + template_err += TTR("Custom debug template not found.") + "\n"; } + } else { + has_export_templates |= exists_export_template("android_debug.apk", &template_err); } + if (p_preset->get("custom_template/release") != "") { rvalid = FileAccess::exists(p_preset->get("custom_template/release")); if (!rvalid) { - err += TTR("Custom release template not found.") + "\n"; + template_err += TTR("Custom release template not found.") + "\n"; } + } else { + has_export_templates |= exists_export_template("android_release.apk", &template_err); } - valid = dvalid || rvalid; + r_missing_templates = !has_export_templates; + valid = dvalid || rvalid || has_export_templates; + if (!valid) { + err += template_err; + } } else { - valid = exists_export_template("android_source.zip", &err); - } - r_missing_templates = !valid; - - // Validate the rest of the configuration. + r_missing_templates = !exists_export_template("android_source.zip", &err); - String adb = EditorSettings::get_singleton()->get("export/android/adb"); - - if (!FileAccess::exists(adb)) { + bool installed_android_build_template = FileAccess::exists("res://android/build/build.gradle"); + if (!installed_android_build_template) { + err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n"; + } - valid = false; - err += TTR("ADB executable not configured in the Editor Settings.") + "\n"; + valid = installed_android_build_template && !r_missing_templates; } - String js = EditorSettings::get_singleton()->get("export/android/jarsigner"); - - if (!FileAccess::exists(js)) { - - valid = false; - err += TTR("OpenJDK jarsigner not configured in the Editor Settings.") + "\n"; - } + // Validate the rest of the configuration. String dk = p_preset->get("keystore/debug"); if (!FileAccess::exists(dk)) { - dk = EditorSettings::get_singleton()->get("export/android/debug_keystore"); if (!FileAccess::exists(dk)) { valid = false; @@ -1718,23 +2043,52 @@ public: } } - if (bool(p_preset->get("custom_template/use_custom_build"))) { - String sdk_path = EditorSettings::get_singleton()->get("export/android/custom_build_sdk_path"); - if (sdk_path == "") { - err += TTR("Custom build requires a valid Android SDK path in Editor Settings.") + "\n"; + String rk = p_preset->get("keystore/release"); + + if (!rk.is_empty() && !FileAccess::exists(rk)) { + valid = false; + err += TTR("Release keystore incorrectly configured in the export preset.") + "\n"; + } + + String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path"); + if (sdk_path == "") { + err += TTR("A valid Android SDK path is required in Editor Settings.") + "\n"; + valid = false; + } else { + Error errn; + // Check for the platform-tools directory. + DirAccessRef da = DirAccess::open(sdk_path.plus_file("platform-tools"), &errn); + if (errn != OK) { + err += TTR("Invalid Android SDK path in Editor Settings."); + err += TTR("Missing 'platform-tools' directory!"); + err += "\n"; + valid = false; + } + + // Validate that adb is available + String adb_path = get_adb_path(); + if (!FileAccess::exists(adb_path)) { + err += TTR("Unable to find Android SDK platform-tools' adb command."); + err += TTR("Please check in the Android SDK directory specified in Editor Settings."); + err += "\n"; valid = false; - } else { - Error errn; - DirAccessRef da = DirAccess::open(sdk_path.plus_file("tools"), &errn); - if (errn != OK) { - err += TTR("Invalid Android SDK path for custom build in Editor Settings.") + "\n"; - valid = false; - } } - if (!FileAccess::exists("res://android/build/build.gradle")) { + // Check for the build-tools directory. + DirAccessRef build_tools_da = DirAccess::open(sdk_path.plus_file("build-tools"), &errn); + if (errn != OK) { + err += TTR("Invalid Android SDK path in Editor Settings."); + err += TTR("Missing 'build-tools' directory!"); + err += "\n"; + valid = false; + } - err += TTR("Android build template not installed in the project. Install it from the Project menu.") + "\n"; + // Validate that apksigner is available + String apksigner_path = get_apksigner_path(); + if (!FileAccess::exists(apksigner_path)) { + err += TTR("Unable to find Android SDK build-tools' apksigner command."); + err += TTR("Please check in the Android SDK directory specified in Editor Settings."); + err += "\n"; valid = false; } } @@ -1742,7 +2096,6 @@ public: bool apk_expansion = p_preset->get("apk_expansion/enable"); if (apk_expansion) { - String apk_expansion_pkey = p_preset->get("apk_expansion/public_key"); if (apk_expansion_pkey == "") { @@ -1756,7 +2109,6 @@ public: String pn_err; if (!is_package_name_valid(get_package_name(pn), &pn_err)) { - valid = false; err += TTR("Invalid package name:") + " " + pn_err + "\n"; } @@ -1767,303 +2119,391 @@ public: err += etc_error; } + // Ensure that `Use Custom Build` is enabled if a plugin is selected. + String enabled_plugins_names = get_plugins_names(get_enabled_plugins(p_preset)); + bool custom_build_enabled = p_preset->get("custom_template/use_custom_build"); + if (!enabled_plugins_names.is_empty() && !custom_build_enabled) { + valid = false; + err += TTR("\"Use Custom Build\" must be enabled to use the plugins."); + err += "\n"; + } + + // Validate the Xr features are properly populated + int xr_mode_index = p_preset->get("xr_features/xr_mode"); + int degrees_of_freedom = p_preset->get("xr_features/degrees_of_freedom"); + int hand_tracking = p_preset->get("xr_features/hand_tracking"); + bool focus_awareness = p_preset->get("xr_features/focus_awareness"); + if (xr_mode_index != /* XRMode.OVR*/ 1) { + if (degrees_of_freedom > 0) { + valid = false; + err += TTR("\"Degrees Of Freedom\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\"."); + err += "\n"; + } + + if (hand_tracking > 0) { + valid = false; + err += TTR("\"Hand Tracking\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\"."); + err += "\n"; + } + + if (focus_awareness) { + valid = false; + err += TTR("\"Focus Awareness\" is only valid when \"Xr Mode\" is \"Oculus Mobile VR\"."); + err += "\n"; + } + } + + if (int(p_preset->get("custom_template/export_format")) == EXPORT_FORMAT_AAB && + !bool(p_preset->get("custom_template/use_custom_build"))) { + valid = false; + err += TTR("\"Export AAB\" is only valid when \"Use Custom Build\" is enabled."); + err += "\n"; + } + r_error = err; return valid; } - virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { + virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override { List<String> list; list.push_back("apk"); + list.push_back("aab"); return list; } - void _update_custom_build_project() { + inline bool is_clean_build_required(Vector<PluginConfigAndroid> enabled_plugins) { + String plugin_names = get_plugins_names(enabled_plugins); + bool first_build = last_custom_build_time == 0; + bool have_plugins_changed = false; + + if (!first_build) { + have_plugins_changed = plugin_names != last_plugin_names; + if (!have_plugins_changed) { + for (int i = 0; i < enabled_plugins.size(); i++) { + if (enabled_plugins.get(i).last_updated > last_custom_build_time) { + have_plugins_changed = true; + break; + } + } + } + } - DirAccessRef da = DirAccess::open("res://android"); + last_custom_build_time = OS::get_singleton()->get_unix_time(); + last_plugin_names = plugin_names; - 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); - } + return have_plugins_changed || first_build; + } - 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); - } - } + String get_apk_expansion_fullpath(const Ref<EditorExportPreset> &p_preset, const String &p_path) { + int version_code = p_preset->get("version/code"); + String package_name = p_preset->get("package/unique_name"); + String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb"; + String fullpath = p_path.get_base_dir().plus_file(apk_file_name); + return fullpath; + } - 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); - } - } - } - } + Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, const String &p_path) { + String fullpath = get_apk_expansion_fullpath(p_preset, p_path); + Error err = save_pack(p_preset, fullpath); + return err; + } + + void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) { + String cmdline = p_preset->get("command_line/extra_args"); + Vector<String> command_line_strings = cmdline.strip_edges().split(" "); + for (int i = 0; i < command_line_strings.size(); i++) { + if (command_line_strings[i].strip_edges().length() == 0) { + command_line_strings.remove(i); + i--; } - d = da->get_next(); } - da->list_dir_end(); - { //fix gradle build + gen_export_flags(command_line_strings, p_flags); - 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; - } - } + bool apk_expansion = p_preset->get("apk_expansion/enable"); + if (apk_expansion) { + String fullpath = get_apk_expansion_fullpath(p_preset, p_path); + String apk_expansion_public_key = p_preset->get("apk_expansion/public_key"); - new_file += "//CHUNK_" + text + "_BEGIN\n"; + command_line_strings.push_back("--use_apk_expansion"); + command_line_strings.push_back("--apk_expansion_md5"); + command_line_strings.push_back(FileAccess::get_md5(fullpath)); + command_line_strings.push_back("--apk_expansion_key"); + command_line_strings.push_back(apk_expansion_public_key.strip_edges()); + } - if (!found) { - ERR_PRINTS("No end marker found in build.gradle for chunk: " + text); - f->seek(pos); - } else { + int xr_mode_index = p_preset->get("xr_features/xr_mode"); + if (xr_mode_index == 1) { + command_line_strings.push_back("--xr_mode_ovr"); + } else { // XRMode.REGULAR is the default. + command_line_strings.push_back("--xr_mode_regular"); + } - //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; - } - } + bool use_32_bit_framebuffer = p_preset->get("graphics/32_bits_framebuffer"); + if (use_32_bit_framebuffer) { + command_line_strings.push_back("--use_depth_32"); + } - new_file += "//DIR_" + text + "_BEGIN\n"; + bool immersive = p_preset->get("screen/immersive_mode"); + if (immersive) { + command_line_strings.push_back("--use_immersive"); + } - if (!found) { - ERR_PRINTS("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 - } + bool debug_opengl = p_preset->get("graphics/opengl_debug"); + if (debug_opengl) { + command_line_strings.push_back("--debug_opengl"); + } - } else { - new_file += l + "\n"; - } - } - } + if (command_line_strings.size()) { + r_command_line_flags.resize(4); + encode_uint32(command_line_strings.size(), &r_command_line_flags.write[0]); + for (int i = 0; i < command_line_strings.size(); i++) { + print_line(itos(i) + " param: " + command_line_strings[i]); + CharString command_line_argument = command_line_strings[i].utf8(); + int base = r_command_line_flags.size(); + int length = command_line_argument.length(); + if (length == 0) + continue; + r_command_line_flags.resize(base + 4 + length); + encode_uint32(length, &r_command_line_flags.write[base]); + copymem(&r_command_line_flags.write[base + 4], command_line_argument.ptr(), length); } - - FileAccessRef f = FileAccess::open("res://android/build/build.gradle", FileAccess::WRITE); - f->store_string(new_file); - f->close(); } + } - { //fix manifest + Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep) { + int export_format = int(p_preset->get("custom_template/export_format")); + String export_label = export_format == EXPORT_FORMAT_AAB ? "AAB" : "APK"; + String release_keystore = p_preset->get("keystore/release"); + String release_username = p_preset->get("keystore/release_user"); + String release_password = p_preset->get("keystore/release_password"); - String 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; - } - } + String apksigner = get_apksigner_path(); + print_verbose("Starting signing of the " + export_label + " binary using " + apksigner); + if (!FileAccess::exists(apksigner)) { + EditorNode::add_io_error("'apksigner' could not be found.\nPlease check the command is available in the Android SDK build-tools directory.\nThe resulting " + export_label + " is unsigned."); + return OK; + } + + String keystore; + String password; + String user; + if (p_debug) { + keystore = p_preset->get("keystore/debug"); + password = p_preset->get("keystore/debug_password"); + user = p_preset->get("keystore/debug_user"); + + if (keystore.is_empty()) { + keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore"); + password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass"); + user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user"); + } - new_file += "<!--CHUNK_" + text + "_BEGIN-->\n"; + if (ep.step("Signing debug " + export_label + "...", 104)) { + return ERR_SKIP; + } - if (!found) { - ERR_PRINTS("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 { + keystore = release_keystore; + password = release_password; + user = release_username; - } 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_PRINTS("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"; - } - } - } + if (ep.step("Signing release " + export_label + "...", 104)) { + return ERR_SKIP; } + } + + if (!FileAccess::exists(keystore)) { + EditorNode::add_io_error("Could not find keystore, unable to export."); + return ERR_FILE_CANT_OPEN; + } - FileAccessRef f = FileAccess::open("res://android/build/AndroidManifest.xml", FileAccess::WRITE); - f->store_string(new_file); - f->close(); + List<String> args; + args.push_back("sign"); + args.push_back("--verbose"); + args.push_back("--ks"); + args.push_back(keystore); + args.push_back("--ks-pass"); + args.push_back("pass:" + password); + args.push_back("--ks-key-alias"); + args.push_back(user); + args.push_back(export_path); + if (p_debug) { + // We only print verbose logs for debug builds to avoid leaking release keystore credentials. + print_verbose("Signing debug binary using: " + String("\n") + apksigner + " " + join_list(args, String(" "))); + } + int retval; + OS::get_singleton()->execute(apksigner, args, nullptr, &retval); + if (retval) { + EditorNode::add_io_error("'apksigner' returned with error #" + itos(retval)); + return ERR_CANT_CREATE; + } + + if (ep.step("Verifying " + export_label + "...", 105)) { + return ERR_SKIP; + } + + args.clear(); + args.push_back("verify"); + args.push_back("--verbose"); + args.push_back(export_path); + if (p_debug) { + print_verbose("Verifying signed build using: " + String("\n") + apksigner + " " + join_list(args, String(" "))); + } + + OS::get_singleton()->execute(apksigner, args, nullptr, &retval); + if (retval) { + EditorNode::add_io_error("'apksigner' verification of " + export_label + " failed."); + return ERR_CANT_CREATE; + } + + print_verbose("Successfully completed signing build."); + return OK; + } + + void _clear_assets_directory() { + DirAccessRef da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (da_res->dir_exists("res://android/build/assets")) { + print_verbose("Clearing assets directory.."); + DirAccessRef da_assets = DirAccess::open("res://android/build/assets"); + da_assets->erase_contents_recursive(); + da_res->remove("res://android/build/assets"); } } - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) { + String join_list(List<String> parts, const String &separator) const { + String ret; + for (int i = 0; i < parts.size(); ++i) { + if (i > 0) { + ret += separator; + } + ret += parts[i]; + } + return ret; + } + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override { + int export_format = int(p_preset->get("custom_template/export_format")); + bool should_sign = p_preset->get("package/signed"); + return export_project_helper(p_preset, p_debug, p_path, export_format, should_sign, p_flags); + } + + Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); String src_apk; + Error err; EditorProgress ep("export", "Exporting for Android", 105, true); - if (bool(p_preset->get("custom_template/use_custom_build"))) { //custom build - //re-generate build.gradle and AndroidManifest.xml + bool use_custom_build = bool(p_preset->get("custom_template/use_custom_build")); + bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG); + bool apk_expansion = p_preset->get("apk_expansion/enable"); + Vector<String> enabled_abis = get_enabled_abis(p_preset); + + print_verbose("Exporting for Android..."); + print_verbose("- debug build: " + bool_to_string(p_debug)); + print_verbose("- export path: " + p_path); + print_verbose("- export format: " + itos(export_format)); + print_verbose("- sign build: " + bool_to_string(should_sign)); + print_verbose("- custom build enabled: " + bool_to_string(use_custom_build)); + print_verbose("- apk expansion enabled: " + bool_to_string(apk_expansion)); + print_verbose("- enabled abis: " + String(",").join(enabled_abis)); + print_verbose("- export filter: " + itos(p_preset->get_export_filter())); + print_verbose("- include filter: " + p_preset->get_include_filter()); + print_verbose("- exclude filter: " + p_preset->get_exclude_filter()); + + Ref<Image> splash_image; + Ref<Image> splash_bg_color_image; + String processed_splash_config_xml = load_splash_refs(splash_image, splash_bg_color_image); + + Ref<Image> main_image; + Ref<Image> foreground; + Ref<Image> background; + + load_icon_refs(p_preset, main_image, foreground, background); + + Vector<uint8_t> command_line_flags; + // Write command line flags into the command_line_flags variable. + get_command_line_flags(p_preset, p_path, p_flags, command_line_flags); + + if (export_format == EXPORT_FORMAT_AAB) { + if (!p_path.ends_with(".aab")) { + EditorNode::get_singleton()->show_warning(TTR("Invalid filename! Android App Bundle requires the *.aab extension.")); + return ERR_UNCONFIGURED; + } + if (apk_expansion) { + EditorNode::get_singleton()->show_warning(TTR("APK Expansion not compatible with Android App Bundle.")); + return ERR_UNCONFIGURED; + } + } + if (export_format == EXPORT_FORMAT_APK && !p_path.ends_with(".apk")) { + EditorNode::get_singleton()->show_warning( + TTR("Invalid filename! Android APK requires the *.apk extension.")); + return ERR_UNCONFIGURED; + } + if (export_format > EXPORT_FORMAT_AAB || export_format < EXPORT_FORMAT_APK) { + EditorNode::add_io_error("Unsupported export format!\n"); + return ERR_UNCONFIGURED; //TODO: is this the right error? + } - { //test that installed build version is alright + if (use_custom_build) { + print_verbose("Starting custom build.."); + //test that installed build version is alright + { + print_verbose("Checking build version.."); FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ); if (!f) { EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu.")); return ERR_UNCONFIGURED; } String version = f->get_line().strip_edges(); + print_verbose("- build version: " + version); + f->close(); if (version != VERSION_FULL_CONFIG) { EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG)); return ERR_UNCONFIGURED; } } - //build project if custom build is enabled - String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path"); - - ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/custom_build_sdk_path'."); - - _update_custom_build_project(); + String sdk_path = EDITOR_GET("export/android/android_sdk_path"); + ERR_FAIL_COND_V_MSG(sdk_path.is_empty(), ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/android_sdk_path'."); + print_verbose("Android sdk path: " + sdk_path); + + // TODO: should we use "package/name" or "application/config/name"? + String project_name = get_project_name(p_preset->get("package/name")); + err = _create_project_name_strings_files(p_preset, project_name); //project name localization. + if (err != OK) { + EditorNode::add_io_error("Unable to overwrite res://android/build/res/*.xml files with project name"); + } + // Copies the project icon files into the appropriate Gradle project directory. + _copy_icons_to_gradle_project(p_preset, processed_splash_config_xml, splash_image, splash_bg_color_image, main_image, foreground, background); + // Write an AndroidManifest.xml file into the Gradle project directory. + _write_tmp_manifest(p_preset, p_give_internet, p_debug); + + //stores all the project files inside the Gradle project directory. Also includes all ABIs + _clear_assets_directory(); + if (!apk_expansion) { + print_verbose("Exporting project files.."); + err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, NULL, ignore_so_file); + if (err != OK) { + EditorNode::add_io_error("Could not export project files to gradle project\n"); + return err; + } + } else { + print_verbose("Saving apk expansion file.."); + err = save_apk_expansion_file(p_preset, p_path); + if (err != OK) { + EditorNode::add_io_error("Could not write expansion package file!"); + return err; + } + } + print_verbose("Storing command line flags.."); + store_file_at_path("res://android/build/assets/_cl_", command_line_flags); + print_verbose("Updating ANDROID_HOME environment to " + sdk_path); OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required - String build_command; + #ifdef WINDOWS_ENABLED build_command = "gradlew.bat"; #else @@ -2071,57 +2511,124 @@ public: #endif String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build"); - build_command = build_path.plus_file(build_command); String package_name = get_package_name(p_preset->get("package/unique_name")); + String version_code = itos(p_preset->get("version/code")); + String version_name = p_preset->get("version/name"); + String enabled_abi_string = String("|").join(enabled_abis); + String sign_flag = should_sign ? "true" : "false"; + String zipalign_flag = "true"; + + Vector<PluginConfigAndroid> enabled_plugins = get_enabled_plugins(p_preset); + String local_plugins_binaries = get_plugins_binaries(PluginConfigAndroid::BINARY_TYPE_LOCAL, enabled_plugins); + String remote_plugins_binaries = get_plugins_binaries(PluginConfigAndroid::BINARY_TYPE_REMOTE, enabled_plugins); + String custom_maven_repos = get_plugins_custom_maven_repos(enabled_plugins); + bool clean_build_required = is_clean_build_required(enabled_plugins); List<String> cmdline; - cmdline.push_back("build"); - cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name. + if (clean_build_required) { + cmdline.push_back("clean"); + } + + String build_type = p_debug ? "Debug" : "Release"; + if (export_format == EXPORT_FORMAT_AAB) { + String bundle_build_command = vformat("bundle%s", build_type); + cmdline.push_back(bundle_build_command); + } else if (export_format == EXPORT_FORMAT_APK) { + String apk_build_command = vformat("assemble%s", build_type); + cmdline.push_back(apk_build_command); + } + cmdline.push_back("-p"); // argument to specify the start directory. cmdline.push_back(build_path); // start directory. - /*{ used for debug - int ec; - String pipe; - OS::get_singleton()->execute(build_command, cmdline, true, NULL, NULL, &ec); - print_line("exit code: " + itos(ec)); + cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name. + cmdline.push_back("-Pexport_version_code=" + version_code); // argument to specify the version code. + cmdline.push_back("-Pexport_version_name=" + version_name); // argument to specify the version name. + cmdline.push_back("-Pexport_enabled_abis=" + enabled_abi_string); // argument to specify enabled ABIs. + cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies. + cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies. + cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies. + cmdline.push_back("-Pperform_zipalign=" + zipalign_flag); // argument to specify whether the build should be zipaligned. + cmdline.push_back("-Pperform_signing=" + sign_flag); // argument to specify whether the build should be signed. + cmdline.push_back("-Pgodot_editor_version=" + String(VERSION_FULL_CONFIG)); + + // NOTE: The release keystore is not included in the verbose logging + // to avoid accidentally leaking sensitive information when sharing verbose logs for troubleshooting. + // Any non-sensitive additions to the command line arguments must be done above this section. + // Sensitive additions must be done below the logging statement. + print_verbose("Build Android project using gradle command: " + String("\n") + build_command + " " + join_list(cmdline, String(" "))); + + if (should_sign && !p_debug) { + // Pass the release keystore info as well + String release_keystore = p_preset->get("keystore/release"); + String release_username = p_preset->get("keystore/release_user"); + String release_password = p_preset->get("keystore/release_password"); + if (!FileAccess::exists(release_keystore)) { + EditorNode::add_io_error("Could not find keystore, unable to export."); + return ERR_FILE_CANT_OPEN; + } + + cmdline.push_back("-Prelease_keystore_file=" + release_keystore); // argument to specify the release keystore file. + cmdline.push_back("-Prelease_keystore_alias=" + release_username); // argument to specify the release keystore alias. + cmdline.push_back("-Prelease_keystore_password=" + release_password); // argument to specity the release keystore password. } - */ + int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline); if (result != 0) { EditorNode::get_singleton()->show_warning(TTR("Building of Android project failed, check output for the error.\nAlternatively visit docs.godotengine.org for Android build documentation.")); return ERR_CANT_CREATE; } - if (p_debug) { - src_apk = build_path.plus_file("build/outputs/apk/debug/android_debug.apk"); - } else { - src_apk = build_path.plus_file("build/outputs/apk/release/android_release.apk"); + + List<String> copy_args; + String copy_command; + if (export_format == EXPORT_FORMAT_AAB) { + copy_command = vformat("copyAndRename%sAab", build_type); + } else if (export_format == EXPORT_FORMAT_APK) { + copy_command = vformat("copyAndRename%sApk", build_type); } - if (!FileAccess::exists(src_apk)) { - EditorNode::get_singleton()->show_warning(TTR("No build apk generated at: ") + "\n" + src_apk); - return ERR_CANT_CREATE; + copy_args.push_back(copy_command); + + copy_args.push_back("-p"); // argument to specify the start directory. + copy_args.push_back(build_path); // start directory. + + String export_filename = p_path.get_file(); + String export_path = p_path.get_base_dir(); + if (export_path.is_rel_path()) { + export_path = OS::get_singleton()->get_resource_dir().plus_file(export_path); } + export_path = ProjectSettings::get_singleton()->globalize_path(export_path).simplify_path(); - } else { + copy_args.push_back("-Pexport_path=file:" + export_path); + copy_args.push_back("-Pexport_filename=" + export_filename); - if (p_debug) - src_apk = p_preset->get("custom_template/debug"); - else - src_apk = p_preset->get("custom_template/release"); + print_verbose("Copying Android binary using gradle command: " + String("\n") + build_command + " " + join_list(copy_args, String(" "))); + int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args); + if (copy_result != 0) { + EditorNode::get_singleton()->show_warning(TTR("Unable to copy and rename export file, check gradle project directory for outputs.")); + return ERR_CANT_CREATE; + } - src_apk = src_apk.strip_edges(); + print_verbose("Successfully completed Android custom build."); + return OK; + } + // This is the start of the Legacy build system + print_verbose("Starting legacy build system.."); + if (p_debug) + src_apk = p_preset->get("custom_template/debug"); + else + src_apk = p_preset->get("custom_template/release"); + src_apk = src_apk.strip_edges(); + if (src_apk == "") { + if (p_debug) { + src_apk = find_export_template("android_debug.apk"); + } else { + src_apk = find_export_template("android_release.apk"); + } if (src_apk == "") { - if (p_debug) { - src_apk = find_export_template("android_debug.apk"); - } else { - src_apk = find_export_template("android_release.apk"); - } - if (src_apk == "") { - EditorNode::add_io_error("Package not found: " + src_apk); - return ERR_FILE_NOT_FOUND; - } + EditorNode::add_io_error("Package not found: " + src_apk); + return ERR_FILE_NOT_FOUND; } } @@ -2129,16 +2636,15 @@ public: return ERR_FILE_BAD_PATH; } - FileAccess *src_f = NULL; + FileAccess *src_f = nullptr; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); - if (ep.step("Creating APK", 0)) { + if (ep.step("Creating APK...", 0)) { return ERR_SKIP; } unzFile pkg = unzOpen2(src_apk.utf8().get_data(), &io); if (!pkg) { - EditorNode::add_io_error("Could not find template APK to export:\n" + src_apk); return ERR_FILE_NOT_FOUND; } @@ -2146,7 +2652,7 @@ public: int ret = unzGoToFirstFile(pkg); zlib_filefunc_def io2 = io; - FileAccess *dst_f = NULL; + FileAccess *dst_f = nullptr; io2.opaque = &dst_f; String tmp_unaligned_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned.apk"); @@ -2157,65 +2663,21 @@ public: return m_err; \ } - zipFile unaligned_apk = zipOpen2(tmp_unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2); - - bool use_32_fb = p_preset->get("graphics/32_bits_framebuffer"); - bool immersive = p_preset->get("screen/immersive_mode"); - bool debug_opengl = p_preset->get("screen/opengl_debug"); - - bool _signed = p_preset->get("package/signed"); - - bool apk_expansion = p_preset->get("apk_expansion/enable"); + zipFile unaligned_apk = zipOpen2(tmp_unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2); String cmdline = p_preset->get("command_line/extra_args"); - int version_code = p_preset->get("version/code"); String version_name = p_preset->get("version/name"); String package_name = p_preset->get("package/unique_name"); String apk_expansion_pkey = p_preset->get("apk_expansion/public_key"); - String release_keystore = p_preset->get("keystore/release"); - String release_username = p_preset->get("keystore/release_user"); - String release_password = p_preset->get("keystore/release_password"); - - Vector<String> enabled_abis = get_enabled_abis(p_preset); - - String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon"); - - // Prepare images to be resized for the icons. If some image ends up being uninitialized, the default image from the export template will be used. - Ref<Image> launcher_icon_image; - Ref<Image> launcher_adaptive_icon_foreground_image; - Ref<Image> launcher_adaptive_icon_background_image; - - launcher_icon_image.instance(); - launcher_adaptive_icon_foreground_image.instance(); - launcher_adaptive_icon_background_image.instance(); - - // Regular icon: user selection -> project icon -> default. - String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges(); - if (path.empty() || ImageLoader::load_image(path, launcher_icon_image) != OK) { - ImageLoader::load_image(project_icon_path, launcher_icon_image); - } - - // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default). - path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges(); - if (path.empty() || ImageLoader::load_image(path, launcher_adaptive_icon_foreground_image) != OK) { - launcher_adaptive_icon_foreground_image = launcher_icon_image; - } - - // Adaptive background: user selection -> default. - path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges(); - if (!path.empty()) { - ImageLoader::load_image(path, launcher_adaptive_icon_background_image); - } - + Vector<String> invalid_abis(enabled_abis); while (ret == UNZ_OK) { - //get filename unz_file_info info; char fname[16384]; - ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0); + ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); bool skip = false; @@ -2230,24 +2692,38 @@ public: unzCloseCurrentFile(pkg); //write - if (file == "AndroidManifest.xml") { - _fix_manifest(p_preset, data, p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG)); + _fix_manifest(p_preset, data, p_give_internet); } - if (file == "resources.arsc") { _fix_resources(p_preset, data); } + // Process the splash image + if (file == SPLASH_IMAGE_EXPORT_PATH && splash_image.is_valid() && !splash_image->is_empty()) { + _load_image_data(splash_image, data); + } + + // Process the splash bg color image + if (file == SPLASH_BG_COLOR_PATH && splash_bg_color_image.is_valid() && !splash_bg_color_image->is_empty()) { + _load_image_data(splash_bg_color_image, data); + } + for (int i = 0; i < icon_densities_count; ++i) { - if (launcher_icon_image.is_valid() && !launcher_icon_image->empty()) { - _process_launcher_icons(file, launcher_icon_image, launcher_icons[i], data); + if (main_image.is_valid() && !main_image->is_empty()) { + if (file == launcher_icons[i].export_path) { + _process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data); + } } - if (launcher_adaptive_icon_foreground_image.is_valid() && !launcher_adaptive_icon_foreground_image->empty()) { - _process_launcher_icons(file, launcher_adaptive_icon_foreground_image, launcher_adaptive_icon_foregrounds[i], data); + if (foreground.is_valid() && !foreground->is_empty()) { + if (file == launcher_adaptive_icon_foregrounds[i].export_path) { + _process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data); + } } - if (launcher_adaptive_icon_background_image.is_valid() && !launcher_adaptive_icon_background_image->empty()) { - _process_launcher_icons(file, launcher_adaptive_icon_background_image, launcher_adaptive_icon_backgrounds[i], data); + if (background.is_valid() && !background->is_empty()) { + if (file == launcher_adaptive_icon_backgrounds[i].export_path) { + _process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data); + } } } @@ -2255,6 +2731,7 @@ public: bool enabled = false; for (int i = 0; i < enabled_abis.size(); ++i) { if (file.begins_with("lib/" + enabled_abis[i] + "/")) { + invalid_abis.erase(enabled_abis[i]); enabled = true; break; } @@ -2264,7 +2741,7 @@ public: } } - if (file.begins_with("META-INF") && _signed) { + if (file.begins_with("META-INF") && should_sign) { skip = true; } @@ -2279,11 +2756,11 @@ public: zipOpenNewFileInZip(unaligned_apk, file.utf8().get_data(), &zipfi, - NULL, + nullptr, 0, - NULL, + nullptr, 0, - NULL, + nullptr, uncompressed ? 0 : Z_DEFLATED, Z_DEFAULT_COMPRESSION); @@ -2294,213 +2771,76 @@ public: ret = unzGoToNextFile(pkg); } - if (ep.step("Adding Files...", 1)) { - CLEANUP_AND_RETURN(ERR_SKIP); - } - Error err = OK; - Vector<String> cl = cmdline.strip_edges().split(" "); - for (int i = 0; i < cl.size(); i++) { - if (cl[i].strip_edges().length() == 0) { - cl.remove(i); - i--; - } + if (!invalid_abis.is_empty()) { + String unsupported_arch = String(", ").join(invalid_abis); + EditorNode::add_io_error("Missing libraries in the export template for the selected architectures: " + unsupported_arch + ".\n" + + "Please build a template with all required libraries, or uncheck the missing architectures in the export preset."); + CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND); } - gen_export_flags(cl, p_flags); + if (ep.step("Adding files...", 1)) { + CLEANUP_AND_RETURN(ERR_SKIP); + } + err = OK; if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { - APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; err = export_project_files(p_preset, ignore_apk_file, &ed, save_apk_so); } else { - //all files - if (apk_expansion) { - - String apkfname = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb"; - String fullpath = p_path.get_base_dir().plus_file(apkfname); - err = save_pack(p_preset, fullpath); - + err = save_apk_expansion_file(p_preset, p_path); if (err != OK) { - unzClose(pkg); - EditorNode::add_io_error("Could not write expansion package file: " + apkfname); - - CLEANUP_AND_RETURN(ERR_SKIP); + EditorNode::add_io_error("Could not write expansion package file!"); + return err; } - - cl.push_back("--use_apk_expansion"); - cl.push_back("--apk_expansion_md5"); - cl.push_back(FileAccess::get_md5(fullpath)); - cl.push_back("--apk_expansion_key"); - cl.push_back(apk_expansion_pkey.strip_edges()); - } else { - APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; - err = export_project_files(p_preset, save_apk_file, &ed, save_apk_so); } } - int xr_mode_index = p_preset->get("xr_features/xr_mode"); - if (xr_mode_index == 1 /* XRMode.OVR */) { - cl.push_back("--xr_mode_ovr"); - } else { - // XRMode.REGULAR is the default. - cl.push_back("--xr_mode_regular"); - } - - if (use_32_fb) - cl.push_back("--use_depth_32"); - - if (immersive) - cl.push_back("--use_immersive"); - - if (debug_opengl) - cl.push_back("--debug_opengl"); - - if (cl.size()) { - //add comandline - Vector<uint8_t> clf; - clf.resize(4); - encode_uint32(cl.size(), &clf.write[0]); - for (int i = 0; i < cl.size(); i++) { - - print_line(itos(i) + " param: " + cl[i]); - CharString txt = cl[i].utf8(); - int base = clf.size(); - int length = txt.length(); - if (!length) - continue; - clf.resize(base + 4 + length); - encode_uint32(length, &clf.write[base]); - copymem(&clf.write[base + 4], txt.ptr(), length); - } - - zip_fileinfo zipfi = get_zip_fileinfo(); - - zipOpenNewFileInZip(unaligned_apk, - "assets/_cl_", - &zipfi, - NULL, - 0, - NULL, - 0, - NULL, - 0, // No compress (little size gain and potentially slower startup) - Z_DEFAULT_COMPRESSION); - - zipWriteInFileInZip(unaligned_apk, clf.ptr(), clf.size()); - zipCloseFileInZip(unaligned_apk); + if (err != OK) { + unzClose(pkg); + EditorNode::add_io_error("Could not export project files"); + CLEANUP_AND_RETURN(ERR_SKIP); } - zipClose(unaligned_apk, NULL); + zip_fileinfo zipfi = get_zip_fileinfo(); + zipOpenNewFileInZip(unaligned_apk, + "assets/_cl_", + &zipfi, + NULL, + 0, + NULL, + 0, + NULL, + 0, // No compress (little size gain and potentially slower startup) + Z_DEFAULT_COMPRESSION); + zipWriteInFileInZip(unaligned_apk, command_line_flags.ptr(), command_line_flags.size()); + zipCloseFileInZip(unaligned_apk); + zipClose(unaligned_apk, nullptr); unzClose(pkg); if (err != OK) { CLEANUP_AND_RETURN(err); } - if (_signed) { - - String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner"); - if (!FileAccess::exists(jarsigner)) { - EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting APK is unsigned."); - CLEANUP_AND_RETURN(OK); - } - - String keystore; - String password; - String user; - if (p_debug) { - - keystore = p_preset->get("keystore/debug"); - password = p_preset->get("keystore/debug_password"); - user = p_preset->get("keystore/debug_user"); - - if (keystore.empty()) { - - keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore"); - password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass"); - user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user"); - } - - if (ep.step("Signing debug APK...", 103)) { - CLEANUP_AND_RETURN(ERR_SKIP); - } - - } else { - keystore = release_keystore; - password = release_password; - user = release_username; - - if (ep.step("Signing release APK...", 103)) { - CLEANUP_AND_RETURN(ERR_SKIP); - } - } - - if (!FileAccess::exists(keystore)) { - EditorNode::add_io_error("Could not find keystore, unable to export."); - CLEANUP_AND_RETURN(ERR_FILE_CANT_OPEN); - } - - List<String> args; - args.push_back("-digestalg"); - args.push_back("SHA-256"); - args.push_back("-sigalg"); - args.push_back("SHA256withRSA"); - String tsa_url = EditorSettings::get_singleton()->get("export/android/timestamping_authority_url"); - if (tsa_url != "") { - args.push_back("-tsa"); - args.push_back(tsa_url); - } - args.push_back("-verbose"); - args.push_back("-keystore"); - args.push_back(keystore); - args.push_back("-storepass"); - args.push_back(password); - args.push_back(tmp_unaligned_path); - args.push_back(user); - int retval; - OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval); - if (retval) { - EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval)); - CLEANUP_AND_RETURN(ERR_CANT_CREATE); - } - - if (ep.step("Verifying APK...", 104)) { - CLEANUP_AND_RETURN(ERR_SKIP); - } - - args.clear(); - args.push_back("-verify"); - args.push_back("-keystore"); - args.push_back(keystore); - args.push_back(tmp_unaligned_path); - args.push_back("-verbose"); - - OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval); - if (retval) { - EditorNode::add_io_error("'jarsigner' verification of APK failed. Make sure to use a jarsigner from OpenJDK 8."); - CLEANUP_AND_RETURN(ERR_CANT_CREATE); - } - } - - // Let's zip-align (must be done after signing) + // Let's zip-align (must be done before signing) static const int ZIP_ALIGNMENT = 4; - if (ep.step("Aligning APK...", 105)) { + // If we're not signing the apk, then the next step should be the last. + const int next_step = should_sign ? 103 : 105; + if (ep.step("Aligning APK...", next_step)) { CLEANUP_AND_RETURN(ERR_SKIP); } unzFile tmp_unaligned = unzOpen2(tmp_unaligned_path.utf8().get_data(), &io); if (!tmp_unaligned) { - EditorNode::add_io_error("Could not unzip temporary unaligned APK."); CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND); } @@ -2508,22 +2848,21 @@ public: ret = unzGoToFirstFile(tmp_unaligned); io2 = io; - dst_f = NULL; + dst_f = nullptr; io2.opaque = &dst_f; - zipFile final_apk = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2); + zipFile final_apk = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2); // Take files from the unaligned APK and write them out to the aligned one // in raw mode, i.e. not uncompressing and recompressing, aligning them as needed, // following what is done in https://github.com/android/platform_build/blob/master/tools/zipalign/ZipAlign.cpp int bias = 0; while (ret == UNZ_OK) { - unz_file_info info; memset(&info, 0, sizeof(info)); char fname[16384]; char extra[16384]; - ret = unzGetCurrentFileInfo(tmp_unaligned, &info, fname, 16384, extra, 16384 - ZIP_ALIGNMENT, NULL, 0); + ret = unzGetCurrentFileInfo(tmp_unaligned, &info, fname, 16384, extra, 16384 - ZIP_ALIGNMENT, nullptr, 0); String file = fname; @@ -2547,17 +2886,15 @@ public: memset(extra + info.size_file_extra, 0, padding); - // write - zip_fileinfo zipfi = get_zip_fileinfo(); - + zip_fileinfo fileinfo = get_zip_fileinfo(); zipOpenNewFileInZip2(final_apk, file.utf8().get_data(), - &zipfi, + &fileinfo, extra, info.size_file_extra + padding, - NULL, + nullptr, 0, - NULL, + nullptr, method, level, 1); // raw write @@ -2569,23 +2906,30 @@ public: ret = unzGoToNextFile(tmp_unaligned); } - zipClose(final_apk, NULL); + zipClose(final_apk, nullptr); unzClose(tmp_unaligned); + if (should_sign) { + // Signing must be done last as any additional modifications to the + // file will invalidate the signature. + err = sign_apk(p_preset, p_debug, p_path, ep); + if (err != OK) { + CLEANUP_AND_RETURN(err); + } + } + CLEANUP_AND_RETURN(OK); } - virtual void get_platform_features(List<String> *r_features) { - + virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("mobile"); r_features->push_back("Android"); } - virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) { + virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { } EditorExportPlatformAndroid() { - Ref<Image> img = memnew(Image(_android_logo)); logo.instance(); logo->create_from_image(img); @@ -2594,40 +2938,31 @@ public: run_icon.instance(); run_icon->create_from_image(img); - device_lock = Mutex::create(); - devices_changed = true; - quit_request = false; - device_thread = Thread::create(_device_poll_thread, this); + devices_changed.set(); + plugins_changed.set(); + check_for_changes_thread.start(_check_for_changes_poll_thread, this); } ~EditorExportPlatformAndroid() { - quit_request = true; - Thread::wait_to_finish(device_thread); - memdelete(device_lock); - memdelete(device_thread); + quit_request.set(); + check_for_changes_thread.wait_to_finish(); } }; void register_android_exporter() { - String exe_ext; if (OS::get_singleton()->get_name() == "Windows") { exe_ext = "*.exe"; } - EDITOR_DEF("export/android/adb", ""); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/adb", PROPERTY_HINT_GLOBAL_FILE, exe_ext)); - EDITOR_DEF("export/android/jarsigner", ""); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/jarsigner", PROPERTY_HINT_GLOBAL_FILE, exe_ext)); + EDITOR_DEF("export/android/android_sdk_path", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); EDITOR_DEF("export/android/debug_keystore", ""); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore")); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks")); EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey"); EDITOR_DEF("export/android/debug_keystore_pass", "android"); EDITOR_DEF("export/android/force_system_user", false); - EDITOR_DEF("export/android/custom_build_sdk_path", ""); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/custom_build_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); - EDITOR_DEF("export/android/timestamping_authority_url", ""); EDITOR_DEF("export/android/shutdown_adb_on_exit", true); Ref<EditorExportPlatformAndroid> exporter = Ref<EditorExportPlatformAndroid>(memnew(EditorExportPlatformAndroid)); diff --git a/platform/android/export/export.h b/platform/android/export/export.h index ce786cc8b6..28e09f41db 100644 --- a/platform/android/export/export.h +++ b/platform/android/export/export.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,4 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#ifndef ANDROID_EXPORT_H +#define ANDROID_EXPORT_H + void register_android_exporter(); + +#endif // ANDROID_EXPORT_H diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h new file mode 100644 index 0000000000..097a2391ee --- /dev/null +++ b/platform/android/export/gradle_export_util.h @@ -0,0 +1,306 @@ +/*************************************************************************/ +/* gradle_export_util.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GODOT_GRADLE_EXPORT_UTIL_H +#define GODOT_GRADLE_EXPORT_UTIL_H + +#include "core/io/zip_io.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "core/os/os.h" +#include "editor/editor_export.h" + +const String godot_project_name_xml_string = R"(<?xml version="1.0" encoding="utf-8"?> +<!--WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> +<resources> + <string name="godot_project_name_string">%s</string> +</resources> +)"; + +DisplayServer::ScreenOrientation _get_screen_orientation() { + String orientation_settings = ProjectSettings::get_singleton()->get("display/window/handheld/orientation"); + DisplayServer::ScreenOrientation screen_orientation; + if (orientation_settings == "portrait") + screen_orientation = DisplayServer::SCREEN_PORTRAIT; + else if (orientation_settings == "reverse_landscape") + screen_orientation = DisplayServer::SCREEN_REVERSE_LANDSCAPE; + else if (orientation_settings == "reverse_portrait") + screen_orientation = DisplayServer::SCREEN_REVERSE_PORTRAIT; + else if (orientation_settings == "sensor_landscape") + screen_orientation = DisplayServer::SCREEN_SENSOR_LANDSCAPE; + else if (orientation_settings == "sensor_portrait") + screen_orientation = DisplayServer::SCREEN_SENSOR_PORTRAIT; + else if (orientation_settings == "sensor") + screen_orientation = DisplayServer::SCREEN_SENSOR; + else + screen_orientation = DisplayServer::SCREEN_LANDSCAPE; + + return screen_orientation; +} + +int _get_android_orientation_value(DisplayServer::ScreenOrientation screen_orientation) { + switch (screen_orientation) { + case DisplayServer::SCREEN_PORTRAIT: + return 1; + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: + return 8; + case DisplayServer::SCREEN_REVERSE_PORTRAIT: + return 9; + case DisplayServer::SCREEN_SENSOR_LANDSCAPE: + return 11; + case DisplayServer::SCREEN_SENSOR_PORTRAIT: + return 12; + case DisplayServer::SCREEN_SENSOR: + return 13; + case DisplayServer::SCREEN_LANDSCAPE: + default: + return 0; + } +} + +String _get_android_orientation_label(DisplayServer::ScreenOrientation screen_orientation) { + switch (screen_orientation) { + case DisplayServer::SCREEN_PORTRAIT: + return "portrait"; + case DisplayServer::SCREEN_REVERSE_LANDSCAPE: + return "reverseLandscape"; + case DisplayServer::SCREEN_REVERSE_PORTRAIT: + return "reversePortrait"; + case DisplayServer::SCREEN_SENSOR_LANDSCAPE: + return "userLandscape"; + case DisplayServer::SCREEN_SENSOR_PORTRAIT: + return "userPortrait"; + case DisplayServer::SCREEN_SENSOR: + return "fullUser"; + case DisplayServer::SCREEN_LANDSCAPE: + default: + return "landscape"; + } +} + +// Utility method used to create a directory. +Error create_directory(const String &p_dir) { + if (!DirAccess::exists(p_dir)) { + DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'."); + Error err = filesystem_da->make_dir_recursive(p_dir); + ERR_FAIL_COND_V_MSG(err, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'."); + memdelete(filesystem_da); + } + return OK; +} + +// Implementation of EditorExportSaveSharedObject. +// This method will only be called as an input to export_project_files. +// This method lets the .so files for all ABIs to be copied +// into the gradle project from the .AAR file +Error ignore_so_file(void *p_userdata, const SharedObject &p_so) { + return OK; +} + +// Writes p_data into a file at p_path, creating directories if necessary. +// Note: this will overwrite the file at p_path if it already exists. +Error store_file_at_path(const String &p_path, const Vector<uint8_t> &p_data) { + String dir = p_path.get_base_dir(); + Error err = create_directory(dir); + if (err != OK) { + return err; + } + FileAccess *fa = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'."); + fa->store_buffer(p_data.ptr(), p_data.size()); + memdelete(fa); + return OK; +} + +// Writes string p_data into a file at p_path, creating directories if necessary. +// Note: this will overwrite the file at p_path if it already exists. +Error store_string_at_path(const String &p_path, const String &p_data) { + String dir = p_path.get_base_dir(); + Error err = create_directory(dir); + if (err != OK) { + if (OS::get_singleton()->is_stdout_verbose()) { + print_error("Unable to write data into " + p_path); + } + return err; + } + FileAccess *fa = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'."); + fa->store_string(p_data); + memdelete(fa); + return OK; +} + +// Implementation of EditorExportSaveFunction. +// This method will only be called as an input to export_project_files. +// It is used by the export_project_files method to save all the asset files into the gradle project. +// It's functionality mirrors that of the method save_apk_file. +// This method will be called ONLY when custom build is enabled. +Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { + String dst_path = p_path.replace_first("res://", "res://android/build/assets/"); + print_verbose("Saving project files from " + p_path + " into " + dst_path); + Error err = store_file_at_path(dst_path, p_data); + return err; +} + +// Creates strings.xml files inside the gradle project for different locales. +Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name) { + print_verbose("Creating strings resources for supported locales for project " + project_name); + // Stores the string into the default values directory. + String processed_default_xml_string = vformat(godot_project_name_xml_string, project_name.xml_escape(true)); + store_string_at_path("res://android/build/res/values/godot_project_name_string.xml", processed_default_xml_string); + + // Searches the Gradle project res/ directory to find all supported locales + DirAccessRef da = DirAccess::open("res://android/build/res"); + if (!da) { + if (OS::get_singleton()->is_stdout_verbose()) { + print_error("Unable to open Android resources directory."); + } + return ERR_CANT_OPEN; + } + da->list_dir_begin(); + while (true) { + String file = da->get_next(); + if (file == "") { + break; + } + if (!file.begins_with("values-")) { + // NOTE: This assumes all directories that start with "values-" are for localization. + continue; + } + String locale = file.replace("values-", "").replace("-r", "_"); + String property_name = "application/config/name_" + locale; + String locale_directory = "res://android/build/res/" + file + "/godot_project_name_string.xml"; + if (ProjectSettings::get_singleton()->has_setting(property_name)) { + String locale_project_name = ProjectSettings::get_singleton()->get(property_name); + String processed_xml_string = vformat(godot_project_name_xml_string, locale_project_name.xml_escape(true)); + print_verbose("Storing project name for locale " + locale + " under " + locale_directory); + store_string_at_path(locale_directory, processed_xml_string); + } else { + // TODO: Once the legacy build system is deprecated we don't need to have xml files for this else branch + store_string_at_path(locale_directory, processed_default_xml_string); + } + } + da->list_dir_end(); + return OK; +} + +String bool_to_string(bool v) { + return v ? "true" : "false"; +} + +String _get_gles_tag() { + bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/driver/driver_name") == "GLES3" && + !ProjectSettings::get_singleton()->get("rendering/driver/fallback_to_gles2"); + return min_gles3 ? " <uses-feature android:glEsVersion=\"0x00030000\" android:required=\"true\" />\n" : ""; +} + +String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset) { + String manifest_screen_sizes = " <supports-screens \n tools:node=\"replace\""; + String sizes[] = { "small", "normal", "large", "xlarge" }; + size_t num_sizes = sizeof(sizes) / sizeof(sizes[0]); + for (size_t i = 0; i < num_sizes; i++) { + String feature_name = vformat("screen/support_%s", sizes[i]); + String feature_support = bool_to_string(p_preset->get(feature_name)); + String xml_entry = vformat("\n android:%sScreens=\"%s\"", sizes[i], feature_support); + manifest_screen_sizes += xml_entry; + } + manifest_screen_sizes += " />\n"; + return manifest_screen_sizes; +} + +String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) { + String manifest_xr_features; + bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; + if (uses_xr) { + int dof_index = p_preset->get("xr_features/degrees_of_freedom"); // 0: none, 1: 3dof and 6dof, 2: 6dof + if (dof_index == 1) { + manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"false\" android:version=\"1\" />\n"; + } else if (dof_index == 2) { + manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n"; + } + int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required + if (hand_tracking_index == 1) { + manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n"; + } else if (hand_tracking_index == 2) { + manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"true\" />\n"; + } + } + return manifest_xr_features; +} + +String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) { + String package_name = p_preset->get("package/unique_name"); + String manifest_instrumentation_text = vformat( + " <instrumentation\n" + " tools:node=\"replace\"\n" + " android:name=\".GodotInstrumentation\"\n" + " android:icon=\"@mipmap/icon\"\n" + " android:label=\"@string/godot_project_name_string\"\n" + " android:targetPackage=\"%s\" />\n", + package_name); + return manifest_instrumentation_text; +} + +String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { + bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; + String orientation = _get_android_orientation_label(_get_screen_orientation()); + String manifest_activity_text = vformat( + " <activity android:name=\"com.godot.game.GodotApp\" " + "tools:replace=\"android:screenOrientation\" " + "android:screenOrientation=\"%s\">\n", + orientation); + if (uses_xr) { + String focus_awareness = bool_to_string(p_preset->get("xr_features/focus_awareness")); + manifest_activity_text += vformat(" <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"%s\" />\n", focus_awareness); + } else { + manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n"; + } + manifest_activity_text += " </activity>\n"; + return manifest_activity_text; +} + +String _get_application_tag(const Ref<EditorExportPreset> &p_preset) { + bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; + String manifest_application_text = + " <application android:label=\"@string/godot_project_name_string\"\n" + " android:allowBackup=\"false\" tools:ignore=\"GoogleAppIndexingWarning\"\n" + " android:icon=\"@mipmap/icon\">\n\n" + " <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n"; + + if (uses_xr) { + manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n"; + } + manifest_application_text += _get_activity_tag(p_preset); + manifest_application_text += " </application>\n"; + return manifest_application_text; +} + +#endif //GODOT_GRADLE_EXPORT_UTIL_H diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp index 965342b364..165d5da3ae 100644 --- a/platform/android/file_access_android.cpp +++ b/platform/android/file_access_android.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,22 +29,19 @@ /*************************************************************************/ #include "file_access_android.h" -#include "core/print_string.h" +#include "core/string/print_string.h" -AAssetManager *FileAccessAndroid::asset_manager = NULL; +AAssetManager *FileAccessAndroid::asset_manager = nullptr; /*void FileAccessAndroid::make_default() { - create_func=create_android; }*/ FileAccess *FileAccessAndroid::create_android() { - return memnew(FileAccessAndroid); } Error FileAccessAndroid::_open(const String &p_path, int p_mode_flags) { - String path = fix_path(p_path).simplify_path(); if (path.begins_with("/")) path = path.substr(1, path.length()); @@ -64,20 +61,17 @@ Error FileAccessAndroid::_open(const String &p_path, int p_mode_flags) { } void FileAccessAndroid::close() { - if (!a) return; AAsset_close(a); - a = NULL; + a = nullptr; } bool FileAccessAndroid::is_open() const { - - return a != NULL; + return a != nullptr; } void FileAccessAndroid::seek(size_t p_position) { - ERR_FAIL_COND(!a); AAsset_seek(a, p_position, SEEK_SET); pos = p_position; @@ -90,29 +84,24 @@ void FileAccessAndroid::seek(size_t p_position) { } void FileAccessAndroid::seek_end(int64_t p_position) { - ERR_FAIL_COND(!a); AAsset_seek(a, p_position, SEEK_END); pos = len + p_position; } size_t FileAccessAndroid::get_position() const { - return pos; } size_t FileAccessAndroid::get_len() const { - return len; } bool FileAccessAndroid::eof_reached() const { - return eof; } uint8_t FileAccessAndroid::get_8() const { - if (pos >= len) { eof = true; return 0; @@ -125,7 +114,6 @@ uint8_t FileAccessAndroid::get_8() const { } int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const { - off_t r = AAsset_read(a, p_dst, p_length); if (pos + p_length > len) { @@ -133,7 +121,6 @@ int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const { } if (r >= 0) { - pos += r; if (pos > len) { pos = len; @@ -143,22 +130,18 @@ int FileAccessAndroid::get_buffer(uint8_t *p_dst, int p_length) const { } Error FileAccessAndroid::get_error() const { - return eof ? ERR_FILE_EOF : OK; //not sure what else it may happen } void FileAccessAndroid::flush() { - ERR_FAIL(); } void FileAccessAndroid::store_8(uint8_t p_dest) { - ERR_FAIL(); } bool FileAccessAndroid::file_exists(const String &p_path) { - String path = fix_path(p_path).simplify_path(); if (path.begins_with("/")) path = path.substr(1, path.length()); @@ -174,11 +157,6 @@ bool FileAccessAndroid::file_exists(const String &p_path) { return true; } -FileAccessAndroid::FileAccessAndroid() { - a = NULL; - eof = false; -} - FileAccessAndroid::~FileAccessAndroid() { close(); } diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h index 6b5ec541fd..56010c918a 100644 --- a/platform/android/file_access_android.h +++ b/platform/android/file_access_android.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -38,12 +38,11 @@ //#include <android_native_app_glue.h> class FileAccessAndroid : public FileAccess { - static FileAccess *create_android(); - mutable AAsset *a; - mutable size_t len; - mutable size_t pos; - mutable bool eof; + mutable AAsset *a = nullptr; + mutable size_t len = 0; + mutable size_t pos = 0; + mutable bool eof = false; public: static AAssetManager *asset_manager; @@ -75,7 +74,6 @@ public: //static void make_default(); - FileAccessAndroid(); ~FileAccessAndroid(); }; diff --git a/platform/android/file_access_jandroid.cpp b/platform/android/file_access_jandroid.cpp deleted file mode 100644 index db3aa4255e..0000000000 --- a/platform/android/file_access_jandroid.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/*************************************************************************/ -/* file_access_jandroid.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "file_access_jandroid.h" -#include "core/os/os.h" -#include "thread_jandroid.h" -#include <unistd.h> - -jobject FileAccessJAndroid::io = NULL; -jclass FileAccessJAndroid::cls; -jmethodID FileAccessJAndroid::_file_open = 0; -jmethodID FileAccessJAndroid::_file_get_size = 0; -jmethodID FileAccessJAndroid::_file_seek = 0; -jmethodID FileAccessJAndroid::_file_read = 0; -jmethodID FileAccessJAndroid::_file_tell = 0; -jmethodID FileAccessJAndroid::_file_eof = 0; -jmethodID FileAccessJAndroid::_file_close = 0; - -FileAccess *FileAccessJAndroid::create_jandroid() { - - return memnew(FileAccessJAndroid); -} - -Error FileAccessJAndroid::_open(const String &p_path, int p_mode_flags) { - - if (is_open()) - close(); - - String path = fix_path(p_path).simplify_path(); - if (path.begins_with("/")) - path = path.substr(1, path.length()); - else if (path.begins_with("res://")) - path = path.substr(6, path.length()); - - JNIEnv *env = ThreadAndroid::get_env(); - - jstring js = env->NewStringUTF(path.utf8().get_data()); - int res = env->CallIntMethod(io, _file_open, js, (p_mode_flags & WRITE) ? true : false); - env->DeleteLocalRef(js); - - OS::get_singleton()->print("fopen: '%s' ret %i\n", path.utf8().get_data(), res); - - if (res <= 0) - return ERR_FILE_CANT_OPEN; - id = res; - - return OK; -} - -void FileAccessJAndroid::close() { - - if (!is_open()) - return; - - JNIEnv *env = ThreadAndroid::get_env(); - - env->CallVoidMethod(io, _file_close, id); - id = 0; -} - -bool FileAccessJAndroid::is_open() const { - - return id != 0; -} - -void FileAccessJAndroid::seek(size_t p_position) { - - JNIEnv *env = ThreadAndroid::get_env(); - - ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); - env->CallVoidMethod(io, _file_seek, id, p_position); -} - -void FileAccessJAndroid::seek_end(int64_t p_position) { - - ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); - - seek(get_len()); -} - -size_t FileAccessJAndroid::get_position() const { - - JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - return env->CallIntMethod(io, _file_tell, id); -} - -size_t FileAccessJAndroid::get_len() const { - - JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - return env->CallIntMethod(io, _file_get_size, id); -} - -bool FileAccessJAndroid::eof_reached() const { - - JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - return env->CallIntMethod(io, _file_eof, id); -} - -uint8_t FileAccessJAndroid::get_8() const { - - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - uint8_t byte; - get_buffer(&byte, 1); - return byte; -} -int FileAccessJAndroid::get_buffer(uint8_t *p_dst, int p_length) const { - - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - if (p_length == 0) - return 0; - JNIEnv *env = ThreadAndroid::get_env(); - - jbyteArray jca = (jbyteArray)env->CallObjectMethod(io, _file_read, id, p_length); - - int len = env->GetArrayLength(jca); - env->GetByteArrayRegion(jca, 0, len, (jbyte *)p_dst); - env->DeleteLocalRef((jobject)jca); - - return len; -} - -Error FileAccessJAndroid::get_error() const { - - if (eof_reached()) - return ERR_FILE_EOF; - return OK; -} - -void FileAccessJAndroid::flush() { -} - -void FileAccessJAndroid::store_8(uint8_t p_dest) { -} - -bool FileAccessJAndroid::file_exists(const String &p_path) { - - JNIEnv *env = ThreadAndroid::get_env(); - - String path = fix_path(p_path).simplify_path(); - if (path.begins_with("/")) - path = path.substr(1, path.length()); - else if (path.begins_with("res://")) - path = path.substr(6, path.length()); - - jstring js = env->NewStringUTF(path.utf8().get_data()); - int res = env->CallIntMethod(io, _file_open, js, false); - if (res <= 0) { - env->DeleteLocalRef(js); - return false; - } - env->CallVoidMethod(io, _file_close, res); - env->DeleteLocalRef(js); - return true; -} - -void FileAccessJAndroid::setup(jobject p_io) { - - io = p_io; - JNIEnv *env = ThreadAndroid::get_env(); - - jclass c = env->GetObjectClass(io); - cls = (jclass)env->NewGlobalRef(c); - - _file_open = env->GetMethodID(cls, "file_open", "(Ljava/lang/String;Z)I"); - _file_get_size = env->GetMethodID(cls, "file_get_size", "(I)I"); - _file_tell = env->GetMethodID(cls, "file_tell", "(I)I"); - _file_eof = env->GetMethodID(cls, "file_eof", "(I)Z"); - _file_seek = env->GetMethodID(cls, "file_seek", "(II)V"); - _file_read = env->GetMethodID(cls, "file_read", "(II)[B"); - _file_close = env->GetMethodID(cls, "file_close", "(I)V"); -} - -FileAccessJAndroid::FileAccessJAndroid() { - - id = 0; -} - -FileAccessJAndroid::~FileAccessJAndroid() { - - if (is_open()) - close(); -} diff --git a/platform/android/file_access_jandroid.h b/platform/android/file_access_jandroid.h deleted file mode 100644 index b361c64922..0000000000 --- a/platform/android/file_access_jandroid.h +++ /dev/null @@ -1,84 +0,0 @@ -/*************************************************************************/ -/* file_access_jandroid.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 FILE_ACCESS_JANDROID_H -#define FILE_ACCESS_JANDROID_H - -#include "core/os/file_access.h" -#include "java_godot_lib_jni.h" -class FileAccessJAndroid : public FileAccess { - - static jobject io; - static jclass cls; - - static jmethodID _file_open; - static jmethodID _file_get_size; - static jmethodID _file_seek; - static jmethodID _file_tell; - static jmethodID _file_eof; - static jmethodID _file_read; - static jmethodID _file_close; - - int id; - static FileAccess *create_jandroid(); - -public: - virtual Error _open(const String &p_path, int p_mode_flags); ///< open a file - virtual void close(); ///< close a file - virtual bool is_open() const; ///< true when file is open - - virtual void seek(size_t p_position); ///< seek to a given position - virtual void seek_end(int64_t p_position = 0); ///< seek from the end of file - virtual size_t get_position() const; ///< get position in the file - virtual size_t get_len() const; ///< get size of the file - - virtual bool eof_reached() const; ///< reading passed EOF - - virtual uint8_t get_8() const; ///< get a byte - virtual int get_buffer(uint8_t *p_dst, int p_length) const; - - virtual Error get_error() const; ///< get last error - - virtual void flush(); - virtual void store_8(uint8_t p_dest); ///< store a byte - - virtual bool file_exists(const String &p_path); ///< return true if a file exists - - static void setup(jobject p_io); - - virtual uint64_t _get_modified_time(const String &p_file) { return 0; } - virtual uint32_t _get_unix_permissions(const String &p_file) { return 0; } - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) { return FAILED; } - - FileAccessJAndroid(); - ~FileAccessJAndroid(); -}; - -#endif // FILE_ACCESS_JANDROID_H diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 7e9ce70d80..948fa8c00b 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -6,28 +6,26 @@ 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" android:largeScreens="true" android:xlargeScreens="true" /> - <!-- glEsVersion is modified by the exporter, changing this value here has no effect. --> <uses-feature 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--> + <application + android:label="@string/godot_project_name_string" + android:allowBackup="false" + tools:ignore="GoogleAppIndexingWarning" + android:icon="@mipmap/icon" > - <!-- 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" > + <!-- Records the version of the Godot editor used for building --> + <meta-data + android:name="org.godotengine.editor.version" + android:value="${godotEditorVersion}" /> <!-- 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. --> @@ -40,23 +38,22 @@ <activity android:name=".GodotApp" android:label="@string/godot_project_name_string" - android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" + android:theme="@style/GodotAppSplashTheme" android:launchMode="singleTask" android:screenOrientation="landscape" android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode" android:resizeableActivity="false" tools:ignore="UnusedAttribute" > + <!-- Focus awareness metadata is updated at export time if the user enables it in the 'Xr Features' section. --> + <meta-data android:name="com.oculus.vr.focusaware" android:value="false" /> + <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </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 258ca9197a..934c4bf441 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,35 +21,78 @@ allprojects { mavenCentral() google() jcenter() -//CHUNK_ALLPROJECTS_REPOSITORIES_BEGIN -//CHUNK_ALLPROJECTS_REPOSITORIES_END + + // Godot user plugins custom maven repos + String[] mavenRepos = getGodotPluginsMavenRepos() + if (mavenRepos != null && mavenRepos.size() > 0) { + for (String repoUrl : mavenRepos) { + maven { + url repoUrl + } + } + } } } 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 user plugins remote dependencies + String[] remoteDeps = getGodotPluginsRemoteBinaries() + if (remoteDeps != null && remoteDeps.size() > 0) { + for (String dep : remoteDeps) { + implementation dep + } + } + + // Godot user plugins local dependencies + String[] pluginsBinaries = getGodotPluginsLocalBinaries() + if (pluginsBinaries != null && pluginsBinaries.size() > 0) { + implementation files(pluginsBinaries) + } } android { compileSdkVersion versions.compileSdk buildToolsVersion versions.buildTools + compileOptions { + sourceCompatibility versions.javaVersion + targetCompatibility versions.javaVersion + } + defaultConfig { + // The default ignore pattern for the 'assets' directory includes hidden files and directories which are used by Godot projects. + aaptOptions { + ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~" + } + + ndk { + String[] export_abi_list = getExportEnabledABIs() + abiFilters export_abi_list + } + + manifestPlaceholders = [godotEditorVersion: getGodotEditorVersion()] + // Feel free to modify the application id to your own. applicationId getExportPackageName() + versionCode getExportVersionCode() + versionName getExportVersionName() minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk -//CHUNK_ANDROID_DEFAULTCONFIG_BEGIN -//CHUNK_ANDROID_DEFAULTCONFIG_END } lintOptions { @@ -63,51 +100,63 @@ android { disable 'MissingTranslation', 'UnusedResources' } + ndkVersion versions.ndkVersion + packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' + + // Should be uncommented for development purpose within Android Studio + // doNotStrip '**/*.so' + } + + signingConfigs { + release { + File keystoreFile = new File(getReleaseKeystoreFile()) + if (keystoreFile.isFile()) { + storeFile keystoreFile + storePassword getReleaseKeystorePassword() + keyAlias getReleaseKeyAlias() + keyPassword getReleaseKeystorePassword() + } + } } - // Both signing and zip-aligning will be done at export time - buildTypes.all { buildType -> - buildType.zipAlignEnabled false - buildType.signingConfig null + buildTypes { + + debug { + // Signing and zip-aligning are skipped for prebuilt builds, but + // performed for custom builds. + zipAlignEnabled shouldZipAlign() + if (shouldSign()) { + signingConfig signingConfigs.debug + } else { + signingConfig null + } + } + + release { + // Signing and zip-aligning are skipped for prebuilt builds, but + // performed for custom builds. + zipAlignEnabled shouldZipAlign() + if (shouldSign()) { + signingConfig signingConfigs.release + } else { + signingConfig null + } + } } sourceSets { 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 -> @@ -117,5 +166,26 @@ android { } } -//CHUNK_GLOBAL_BEGIN -//CHUNK_GLOBAL_END +task copyAndRenameDebugApk(type: Copy) { + from "$buildDir/outputs/apk/debug/android_debug.apk" + into getExportPath() + rename "android_debug.apk", getExportFilename() +} + +task copyAndRenameReleaseApk(type: Copy) { + from "$buildDir/outputs/apk/release/android_release.apk" + into getExportPath() + rename "android_release.apk", getExportFilename() +} + +task copyAndRenameDebugAab(type: Copy) { + from "$buildDir/outputs/bundle/debug/build-debug.aab" + into getExportPath() + rename "build-debug.aab", getExportFilename() +} + +task copyAndRenameReleaseAab(type: Copy) { + from "$buildDir/outputs/bundle/release/build-release.aab" + into getExportPath() + rename "build-release.aab", getExportFilename() +} diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 862a954fac..585e517631 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,22 +1,226 @@ ext.versions = [ - androidGradlePlugin : '3.4.2', - compileSdk : 28, - minSdk : 18, - targetSdk : 28, - buildTools : '28.0.3', + androidGradlePlugin: '4.0.1', + compileSdk : 29, + minSdk : 18, + targetSdk : 29, + buildTools : '30.0.1', + supportCoreUtils : '1.0.0', + kotlinVersion : '1.4.10', + v4Support : '1.0.0', + javaVersion : 1.8, + ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated. ] ext.libraries = [ - androidGradlePlugin : "com.android.tools.build:gradle:$versions.androidGradlePlugin" + androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin", + supportCoreUtils : "androidx.legacy:legacy-support-core-utils:$versions.supportCoreUtils", + kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion", + kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlinVersion", + v4Support : "androidx.legacy:legacy-support-v4:$versions.v4Support" ] 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 +} + +ext.getExportVersionCode = { -> + String versionCode = project.hasProperty("export_version_code") ? project.property("export_version_code") : "" + if (versionCode == null || versionCode.isEmpty()) { + versionCode = "1" + } + try { + return Integer.parseInt(versionCode) + } catch (NumberFormatException ignored) { + return 1 + } +} + +ext.getExportVersionName = { -> + String versionName = project.hasProperty("export_version_name") ? project.property("export_version_name") : "" + if (versionName == null || versionName.isEmpty()) { + versionName = "1.0" + } + return versionName +} + +ext.getGodotEditorVersion = { -> + String editorVersion = project.hasProperty("godot_editor_version") ? project.property("godot_editor_version") : "" + if (editorVersion == null || editorVersion.isEmpty()) { + // Try the library version first + editorVersion = getGodotLibraryVersion() + + if (editorVersion.isEmpty()) { + // Fallback value. + editorVersion = "custom_build" + } + } + return editorVersion +} + +ext.getGodotLibraryVersion = { -> + // Attempt to read the version from the `version.py` file. + String libraryVersion = "" + + File versionFile = new File("../../../version.py") + if (versionFile.isFile()) { + List<String> requiredKeys = ["major", "minor", "patch", "status", "module_config"] + def map = [:] + + List<String> lines = versionFile.readLines() + for (String line in lines) { + String[] keyValue = line.split("=") + String key = keyValue[0].trim() + String value = keyValue[1].trim().replaceAll("\"", "") + + if (requiredKeys.contains(key)) { + if (!value.isEmpty()) { + map[key] = value + } + requiredKeys.remove(key) + } + } + + if (requiredKeys.empty) { + libraryVersion = map.values().join(".") + } + } + + if (libraryVersion.isEmpty()) { + // Fallback value in case we're unable to read the file. + libraryVersion = "custom_build" + } + return libraryVersion +} + +final String VALUE_SEPARATOR_REGEX = "\\|" + +// get the list of ABIs the project should be exported to +ext.getExportEnabledABIs = { -> + String enabledABIs = project.hasProperty("export_enabled_abis") ? project.property("export_enabled_abis") : ""; + if (enabledABIs == null || enabledABIs.isEmpty()) { + enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|" + } + Set<String> exportAbiFilter = []; + for (String abi_name : enabledABIs.split(VALUE_SEPARATOR_REGEX)) { + if (!abi_name.trim().isEmpty()){ + exportAbiFilter.add(abi_name); + } + } + return exportAbiFilter; +} + +ext.getExportPath = { + String exportPath = project.hasProperty("export_path") ? project.property("export_path") : "" + if (exportPath == null || exportPath.isEmpty()) { + exportPath = "." + } + return exportPath +} + +ext.getExportFilename = { + String exportFilename = project.hasProperty("export_filename") ? project.property("export_filename") : "" + if (exportFilename == null || exportFilename.isEmpty()) { + exportFilename = "godot_android" + } + return exportFilename +} + +/** + * Parse the project properties for the 'plugins_maven_repos' property and return the list + * of maven repos. + */ +ext.getGodotPluginsMavenRepos = { -> + Set<String> mavenRepos = [] + + // Retrieve the list of maven repos. + if (project.hasProperty("plugins_maven_repos")) { + String mavenReposProperty = project.property("plugins_maven_repos") + if (mavenReposProperty != null && !mavenReposProperty.trim().isEmpty()) { + for (String mavenRepoUrl : mavenReposProperty.split(VALUE_SEPARATOR_REGEX)) { + mavenRepos += mavenRepoUrl.trim() + } + } + } + + return mavenRepos +} + +/** + * Parse the project properties for the 'plugins_remote_binaries' property and return + * it for inclusion in the build dependencies. + */ +ext.getGodotPluginsRemoteBinaries = { -> + Set<String> remoteDeps = [] + + // Retrieve the list of remote plugins binaries. + if (project.hasProperty("plugins_remote_binaries")) { + String remoteDepsList = project.property("plugins_remote_binaries") + if (remoteDepsList != null && !remoteDepsList.trim().isEmpty()) { + for (String dep: remoteDepsList.split(VALUE_SEPARATOR_REGEX)) { + remoteDeps += dep.trim() + } + } + } + return remoteDeps +} + +/** + * Parse the project properties for the 'plugins_local_binaries' property and return + * their binaries for inclusion in the build dependencies. + */ +ext.getGodotPluginsLocalBinaries = { -> + Set<String> binDeps = [] + + // Retrieve the list of local plugins binaries. + if (project.hasProperty("plugins_local_binaries")) { + String pluginsList = project.property("plugins_local_binaries") + if (pluginsList != null && !pluginsList.trim().isEmpty()) { + for (String plugin : pluginsList.split(VALUE_SEPARATOR_REGEX)) { + binDeps += plugin.trim() + } + } + } + + return binDeps +} + +ext.getReleaseKeystoreFile = { -> + String keystoreFile = project.hasProperty("release_keystore_file") ? project.property("release_keystore_file") : "" + if (keystoreFile == null || keystoreFile.isEmpty()) { + keystoreFile = "." + } + return keystoreFile +} + +ext.getReleaseKeystorePassword = { -> + String keystorePassword = project.hasProperty("release_keystore_password") ? project.property("release_keystore_password") : "" + return keystorePassword +} + +ext.getReleaseKeyAlias = { -> + String keyAlias = project.hasProperty("release_keystore_alias") ? project.property("release_keystore_alias") : "" + return keyAlias +} + +ext.shouldZipAlign = { -> + String zipAlignFlag = project.hasProperty("perform_zipalign") ? project.property("perform_zipalign") : "" + if (zipAlignFlag == null || zipAlignFlag.isEmpty()) { + zipAlignFlag = "false" + } + return Boolean.parseBoolean(zipAlignFlag) +} + +ext.shouldSign = { -> + String signFlag = project.hasProperty("perform_signing") ? project.property("perform_signing") : "" + if (signFlag == null || signFlag.isEmpty()) { + signFlag = "false" + } + return Boolean.parseBoolean(signFlag) } diff --git a/platform/android/java/app/res/drawable/splash.png b/platform/android/java/app/res/drawable/splash.png Binary files differnew file mode 100644 index 0000000000..7bddd4325a --- /dev/null +++ b/platform/android/java/app/res/drawable/splash.png diff --git a/platform/android/java/app/res/drawable/splash_bg_color.png b/platform/android/java/app/res/drawable/splash_bg_color.png Binary files differnew file mode 100644 index 0000000000..004b6fd508 --- /dev/null +++ b/platform/android/java/app/res/drawable/splash_bg_color.png diff --git a/platform/android/java/app/res/drawable/splash_drawable.xml b/platform/android/java/app/res/drawable/splash_drawable.xml new file mode 100644 index 0000000000..30627b998c --- /dev/null +++ b/platform/android/java/app/res/drawable/splash_drawable.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + + <item android:drawable="@drawable/splash_bg_color" /> + + <item> + <bitmap + android:gravity="center" + android:filter="false" + android:src="@drawable/splash" /> + </item> +</layer-list> diff --git a/platform/android/java/lib/res/values-ar/strings.xml b/platform/android/java/app/res/values-ar/godot_project_name_string.xml index 9f3dc6d6ac..23aa5cf3e1 100644 --- a/platform/android/java/lib/res/values-ar/strings.xml +++ b/platform/android/java/app/res/values-ar/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-ar</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-bg/strings.xml b/platform/android/java/app/res/values-bg/godot_project_name_string.xml index bd8109277e..dbb7e04ae5 100644 --- a/platform/android/java/lib/res/values-bg/strings.xml +++ b/platform/android/java/app/res/values-bg/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-bg</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-ca/strings.xml b/platform/android/java/app/res/values-ca/godot_project_name_string.xml index 494cb88468..709d0961e6 100644 --- a/platform/android/java/lib/res/values-ca/strings.xml +++ b/platform/android/java/app/res/values-ca/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-ca</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-cs/strings.xml b/platform/android/java/app/res/values-cs/godot_project_name_string.xml index 30ce00f895..ab248a8032 100644 --- a/platform/android/java/lib/res/values-cs/strings.xml +++ b/platform/android/java/app/res/values-cs/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-cs</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-da/strings.xml b/platform/android/java/app/res/values-da/godot_project_name_string.xml index 4c2a1cf0f4..906bf44f57 100644 --- a/platform/android/java/lib/res/values-da/strings.xml +++ b/platform/android/java/app/res/values-da/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-da</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-de/strings.xml b/platform/android/java/app/res/values-de/godot_project_name_string.xml index 52946d4cce..0cacb0175f 100644 --- a/platform/android/java/lib/res/values-de/strings.xml +++ b/platform/android/java/app/res/values-de/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-de</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-el/strings.xml b/platform/android/java/app/res/values-el/godot_project_name_string.xml index 181dc51762..047de616a5 100644 --- a/platform/android/java/lib/res/values-el/strings.xml +++ b/platform/android/java/app/res/values-el/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-el</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-en/strings.xml b/platform/android/java/app/res/values-en/godot_project_name_string.xml index 976a565013..bb3a5dbef3 100644 --- a/platform/android/java/lib/res/values-en/strings.xml +++ b/platform/android/java/app/res/values-en/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-en</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-es-rES/strings.xml b/platform/android/java/app/res/values-es-rES/godot_project_name_string.xml index 73f63a08f8..d4537f3496 100644 --- a/platform/android/java/lib/res/values-es-rES/strings.xml +++ b/platform/android/java/app/res/values-es-rES/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-es_ES</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-es/strings.xml b/platform/android/java/app/res/values-es/godot_project_name_string.xml index 07b718a641..d63a16022e 100644 --- a/platform/android/java/lib/res/values-es/strings.xml +++ b/platform/android/java/app/res/values-es/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-es</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/app/res/values-fa/godot_project_name_string.xml b/platform/android/java/app/res/values-fa/godot_project_name_string.xml new file mode 100644 index 0000000000..c303f13d5f --- /dev/null +++ b/platform/android/java/app/res/values-fa/godot_project_name_string.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> +<resources> + <string name="godot_project_name_string">godot-project-name-fa</string> +</resources> diff --git a/platform/android/java/lib/res/values-fi/strings.xml b/platform/android/java/app/res/values-fi/godot_project_name_string.xml index 323d82aff1..bd6005574a 100644 --- a/platform/android/java/lib/res/values-fi/strings.xml +++ b/platform/android/java/app/res/values-fi/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-fi</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-fr/strings.xml b/platform/android/java/app/res/values-fr/godot_project_name_string.xml index 32bead2661..2e94b65a20 100644 --- a/platform/android/java/lib/res/values-fr/strings.xml +++ b/platform/android/java/app/res/values-fr/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-fr</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-hi/strings.xml b/platform/android/java/app/res/values-hi/godot_project_name_string.xml index 8aab2a8c63..0bf75dcd56 100644 --- a/platform/android/java/lib/res/values-hi/strings.xml +++ b/platform/android/java/app/res/values-hi/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-hi</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-hr/strings.xml b/platform/android/java/app/res/values-hr/godot_project_name_string.xml index caf55e2241..d3f75910f9 100644 --- a/platform/android/java/lib/res/values-hr/strings.xml +++ b/platform/android/java/app/res/values-hr/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-hr</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-hu/strings.xml b/platform/android/java/app/res/values-hu/godot_project_name_string.xml index e7f9e51226..012b613af3 100644 --- a/platform/android/java/lib/res/values-hu/strings.xml +++ b/platform/android/java/app/res/values-hu/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-hu</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/app/res/values-in/godot_project_name_string.xml b/platform/android/java/app/res/values-in/godot_project_name_string.xml new file mode 100644 index 0000000000..eedecff7a1 --- /dev/null +++ b/platform/android/java/app/res/values-in/godot_project_name_string.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> +<resources> + <string name="godot_project_name_string">godot-project-name-in</string> +</resources> diff --git a/platform/android/java/lib/res/values-it/strings.xml b/platform/android/java/app/res/values-it/godot_project_name_string.xml index 1f5e5a049e..7e734047c4 100644 --- a/platform/android/java/lib/res/values-it/strings.xml +++ b/platform/android/java/app/res/values-it/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-it</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/app/res/values-iw/godot_project_name_string.xml b/platform/android/java/app/res/values-iw/godot_project_name_string.xml new file mode 100644 index 0000000000..03893f0cbb --- /dev/null +++ b/platform/android/java/app/res/values-iw/godot_project_name_string.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> +<resources> + <string name="godot_project_name_string">godot-project-name-iw</string> +</resources> diff --git a/platform/android/java/lib/res/values-ja/strings.xml b/platform/android/java/app/res/values-ja/godot_project_name_string.xml index 7f85f57df7..f9dd4fab0d 100644 --- a/platform/android/java/lib/res/values-ja/strings.xml +++ b/platform/android/java/app/res/values-ja/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-ja</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/app/res/values-ko/godot_project_name_string.xml b/platform/android/java/app/res/values-ko/godot_project_name_string.xml new file mode 100644 index 0000000000..26f5dac176 --- /dev/null +++ b/platform/android/java/app/res/values-ko/godot_project_name_string.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> +<resources> + <string name="godot_project_name_string">godot-project-name-ko</string> +</resources> diff --git a/platform/android/java/lib/res/values-lt/strings.xml b/platform/android/java/app/res/values-lt/godot_project_name_string.xml index 6e3677fde7..1c2e976cc5 100644 --- a/platform/android/java/lib/res/values-lt/strings.xml +++ b/platform/android/java/app/res/values-lt/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-lt</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-lv/strings.xml b/platform/android/java/app/res/values-lv/godot_project_name_string.xml index 701fc271ac..b5e638ed73 100644 --- a/platform/android/java/lib/res/values-lv/strings.xml +++ b/platform/android/java/app/res/values-lv/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-lv</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-nb/strings.xml b/platform/android/java/app/res/values-nb/godot_project_name_string.xml index 73147ca1af..e6d89d6a3f 100644 --- a/platform/android/java/lib/res/values-nb/strings.xml +++ b/platform/android/java/app/res/values-nb/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-nb</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-nl/strings.xml b/platform/android/java/app/res/values-nl/godot_project_name_string.xml index e501928a35..93cb3a3878 100644 --- a/platform/android/java/lib/res/values-nl/strings.xml +++ b/platform/android/java/app/res/values-nl/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-nl</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-pl/strings.xml b/platform/android/java/app/res/values-pl/godot_project_name_string.xml index ea5da73b6f..e5d6ac74fb 100644 --- a/platform/android/java/lib/res/values-pl/strings.xml +++ b/platform/android/java/app/res/values-pl/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-pl</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-pt/strings.xml b/platform/android/java/app/res/values-pt/godot_project_name_string.xml index bdda7cd2c7..a4624655c5 100644 --- a/platform/android/java/lib/res/values-pt/strings.xml +++ b/platform/android/java/app/res/values-pt/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-pt</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-ro/strings.xml b/platform/android/java/app/res/values-ro/godot_project_name_string.xml index 3686da4c19..19e026637e 100644 --- a/platform/android/java/lib/res/values-ro/strings.xml +++ b/platform/android/java/app/res/values-ro/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-ro</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-ru/strings.xml b/platform/android/java/app/res/values-ru/godot_project_name_string.xml index 954067658b..284845241f 100644 --- a/platform/android/java/lib/res/values-ru/strings.xml +++ b/platform/android/java/app/res/values-ru/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-ru</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-sk/strings.xml b/platform/android/java/app/res/values-sk/godot_project_name_string.xml index 37d1283124..f8ab4a5b59 100644 --- a/platform/android/java/lib/res/values-sk/strings.xml +++ b/platform/android/java/app/res/values-sk/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-sk</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-sl/strings.xml b/platform/android/java/app/res/values-sl/godot_project_name_string.xml index 0bb249c375..98bd53e8d2 100644 --- a/platform/android/java/lib/res/values-sl/strings.xml +++ b/platform/android/java/app/res/values-sl/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-sl</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-sr/strings.xml b/platform/android/java/app/res/values-sr/godot_project_name_string.xml index 0e83cab1a1..3f400f2a4d 100644 --- a/platform/android/java/lib/res/values-sr/strings.xml +++ b/platform/android/java/app/res/values-sr/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-sr</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-sv/strings.xml b/platform/android/java/app/res/values-sv/godot_project_name_string.xml index e3a04ac2ec..8670b7c9aa 100644 --- a/platform/android/java/lib/res/values-sv/strings.xml +++ b/platform/android/java/app/res/values-sv/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-sv</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-th/strings.xml b/platform/android/java/app/res/values-th/godot_project_name_string.xml index 0aa893b8bf..a1cc1bcd49 100644 --- a/platform/android/java/lib/res/values-th/strings.xml +++ b/platform/android/java/app/res/values-th/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-th</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-tl/strings.xml b/platform/android/java/app/res/values-tl/godot_project_name_string.xml index e7e2af4909..6d66d114cf 100644 --- a/platform/android/java/lib/res/values-tl/strings.xml +++ b/platform/android/java/app/res/values-tl/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-tl</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-tr/strings.xml b/platform/android/java/app/res/values-tr/godot_project_name_string.xml index 97af1243a6..ba3bd7de36 100644 --- a/platform/android/java/lib/res/values-tr/strings.xml +++ b/platform/android/java/app/res/values-tr/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-tr</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-uk/strings.xml b/platform/android/java/app/res/values-uk/godot_project_name_string.xml index 3dea6908a9..5f14ab25a0 100644 --- a/platform/android/java/lib/res/values-uk/strings.xml +++ b/platform/android/java/app/res/values-uk/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-uk</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-vi/strings.xml b/platform/android/java/app/res/values-vi/godot_project_name_string.xml index a6552130b0..295378e111 100644 --- a/platform/android/java/lib/res/values-vi/strings.xml +++ b/platform/android/java/app/res/values-vi/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-vi</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values-zh-rHK/strings.xml b/platform/android/java/app/res/values-zh-rHK/godot_project_name_string.xml index 8a6269da0f..40ab0f285a 100644 --- a/platform/android/java/lib/res/values-zh-rHK/strings.xml +++ b/platform/android/java/app/res/values-zh-rHK/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-zh_HK</string> </resources> diff --git a/platform/android/java/lib/res/values-zh-rTW/strings.xml b/platform/android/java/app/res/values-zh-rTW/godot_project_name_string.xml index b1bb39d5d6..095bd564e2 100644 --- a/platform/android/java/lib/res/values-zh-rTW/strings.xml +++ b/platform/android/java/app/res/values-zh-rTW/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-zh_TW</string> </resources> diff --git a/platform/android/java/lib/res/values-zh-rCN/strings.xml b/platform/android/java/app/res/values-zh/godot_project_name_string.xml index 6668c56bd9..31aa8c273a 100644 --- a/platform/android/java/lib/res/values-zh-rCN/strings.xml +++ b/platform/android/java/app/res/values-zh/godot_project_name_string.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> <resources> <string name="godot_project_name_string">godot-project-name-zh</string> </resources> diff --git a/platform/android/java/app/res/values/godot_project_name_string.xml b/platform/android/java/app/res/values/godot_project_name_string.xml new file mode 100644 index 0000000000..7ec2738896 --- /dev/null +++ b/platform/android/java/app/res/values/godot_project_name_string.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- WARNING: THIS FILE WILL BE OVERWRITTEN AT BUILD TIME--> +<resources> + <string name="godot_project_name_string">godot-project-name</string> +</resources> diff --git a/platform/android/java/app/res/values/themes.xml b/platform/android/java/app/res/values/themes.xml new file mode 100644 index 0000000000..99f723f5ba --- /dev/null +++ b/platform/android/java/app/res/values/themes.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <style name="GodotAppMainTheme" parent="@android:style/Theme.Black.NoTitleBar.Fullscreen"/> + + <style name="GodotAppSplashTheme" parent="@style/GodotAppMainTheme"> + <item name="android:windowBackground">@drawable/splash_drawable</item> + <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> + </style> +</resources> diff --git a/platform/android/java/app/settings.gradle b/platform/android/java/app/settings.gradle new file mode 100644 index 0000000000..33b863c7bf --- /dev/null +++ b/platform/android/java/app/settings.gradle @@ -0,0 +1,2 @@ +// Empty settings.gradle file to denote this directory as being the root project +// of the Godot custom build. diff --git a/platform/android/java/app/src/com/godot/game/GodotApp.java b/platform/android/java/app/src/com/godot/game/GodotApp.java index eb884404cd..955446b0c2 100644 --- a/platform/android/java/app/src/com/godot/game/GodotApp.java +++ b/platform/android/java/app/src/com/godot/game/GodotApp.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,11 +30,18 @@ package com.godot.game; -import org.godotengine.godot.Godot; +import org.godotengine.godot.FullScreenGodotApp; + +import android.os.Bundle; /** * Template activity for Godot Android custom builds. * Feel free to extend and modify this class for your custom logic. */ -public class GodotApp extends Godot { +public class GodotApp extends FullScreenGodotApp { + @Override + public void onCreate(Bundle savedInstanceState) { + setTheme(R.style.GodotAppMainTheme); + super.onCreate(savedInstanceState); + } } diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index 2052017888..ec02b0fc7a 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 } } @@ -21,10 +22,8 @@ allprojects { } ext { - sconsExt = org.gradle.internal.os.OperatingSystem.current().isWindows() ? ".bat" : "" - supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"] - supportedTargets = ['release':"release", 'debug':"release_debug"] + supportedTargets = ["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 +63,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,10 +74,21 @@ 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 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 copyReleaseAAR(type: Copy) { +task copyReleaseAARToAppModule(type: Copy) { dependsOn ':lib:assembleRelease' from('lib/build/outputs/aar') into('app/libs/release') @@ -86,6 +96,17 @@ task copyReleaseAAR(type: Copy) { } /** + * 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'. * The zip file also includes some gradle tools to allow building of the custom build. @@ -95,7 +116,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)) @@ -106,23 +127,28 @@ task zipCustomBuild(type: Zip) { */ task generateGodotTemplates(type: GradleBuild) { // We exclude these gradle tasks so we can run the scons command manually. - for (String buildType : supportedTargets.keySet()) { + for (String buildType : supportedTargets) { startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType) } tasks = [] // Only build the apks and aar files for which we have native shared libraries. - for (String target : supportedTargets.keySet()) { + for (String target : supportedTargets) { 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 +159,24 @@ 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 library generated AAR files - delete("lib/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("$binDir/godot-lib.debug.aar") + delete("$binDir/godot-lib.release.aar") - // Delete the Godot templates in the Godot bin directory - delete("$binDir/android_debug.apk") - delete("$binDir/android_release.apk") - delete("$binDir/android_source.zip") + finalizedBy getTasksByName("clean", true) } diff --git a/platform/android/java/gradle.properties b/platform/android/java/gradle.properties index aac7c9b461..2dc069ad2f 100644 --- a/platform/android/java/gradle.properties +++ b/platform/android/java/gradle.properties @@ -7,6 +7,9 @@ # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html +android.enableJetifier=true +android.useAndroidX=true + # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m @@ -15,3 +18,5 @@ org.gradle.jvmargs=-Xmx1536m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true + +org.gradle.warning.mode=all diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties index bf50265715..a7d8a0f310 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-6.5-all.zip diff --git a/platform/android/java/lib/AndroidManifest.xml b/platform/android/java/lib/AndroidManifest.xml index b133585f99..3034794d69 100644 --- a/platform/android/java/lib/AndroidManifest.xml +++ b/platform/android/java/lib/AndroidManifest.xml @@ -6,6 +6,11 @@ <application> + <!-- Records the version of the Godot library --> + <meta-data + android:name="org.godotengine.library.version" + android:value="${godotLibraryVersion}" /> + <service android:name=".GodotDownloaderService" /> </application> @@ -13,7 +18,7 @@ <instrumentation android:icon="@mipmap/icon" android:label="@string/godot_project_name_string" - android:name=".GodotInstrumentation" + android:name="org.godotengine.godot.GodotInstrumentation" android:targetPackage="org.godotengine.godot" /> </manifest> diff --git a/platform/android/java/lib/aidl/com/android/vending/billing/IInAppBillingService.aidl b/platform/android/java/lib/aidl/com/android/vending/billing/IInAppBillingService.aidl deleted file mode 100644 index 0f2bcae338..0000000000 --- a/platform/android/java/lib/aidl/com/android/vending/billing/IInAppBillingService.aidl +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.vending.billing; - -import android.os.Bundle; - -/** - * InAppBillingService is the service that provides in-app billing version 3 and beyond. - * This service provides the following features: - * 1. Provides a new API to get details of in-app items published for the app including - * price, type, title and description. - * 2. The purchase flow is synchronous and purchase information is available immediately - * after it completes. - * 3. Purchase information of in-app purchases is maintained within the Google Play system - * till the purchase is consumed. - * 4. An API to consume a purchase of an inapp item. All purchases of one-time - * in-app items are consumable and thereafter can be purchased again. - * 5. An API to get current purchases of the user immediately. This will not contain any - * consumed purchases. - * - * All calls will give a response code with the following possible values - * RESULT_OK = 0 - success - * RESULT_USER_CANCELED = 1 - User pressed back or canceled a dialog - * RESULT_SERVICE_UNAVAILABLE = 2 - The network connection is down - * RESULT_BILLING_UNAVAILABLE = 3 - This billing API version is not supported for the type requested - * RESULT_ITEM_UNAVAILABLE = 4 - Requested SKU is not available for purchase - * RESULT_DEVELOPER_ERROR = 5 - Invalid arguments provided to the API - * RESULT_ERROR = 6 - Fatal error during the API action - * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned - * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned - */ -interface IInAppBillingService { - /** - * Checks support for the requested billing API version, package and in-app type. - * Minimum API version supported by this interface is 3. - * @param apiVersion billing API version that the app is using - * @param packageName the package name of the calling app - * @param type type of the in-app item being purchased ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @return RESULT_OK(0) on success and appropriate response code on failures. - */ - int isBillingSupported(int apiVersion, String packageName, String type); - - /** - * Provides details of a list of SKUs - * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle - * with a list JSON strings containing the productId, price, title and description. - * This API can be called with a maximum of 20 SKUs. - * @param apiVersion billing API version that the app is using - * @param packageName the package name of the calling app - * @param type of the in-app items ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "DETAILS_LIST" with a StringArrayList containing purchase information - * in JSON format similar to: - * '{ "productId" : "exampleSku", - * "type" : "inapp", - * "price" : "$5.00", - * "price_currency": "USD", - * "price_amount_micros": 5000000, - * "title : "Example Title", - * "description" : "This is an example description" }' - */ - Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle); - - /** - * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, - * the type, a unique purchase token and an optional developer payload. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param sku the SKU of the in-app item as published in the developer console - * @param type of the in-app item being purchased ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param developerPayload optional argument to be sent back with the purchase information - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "BUY_INTENT" - PendingIntent to start the purchase flow - * - * The Pending intent should be launched with startIntentSenderForResult. When purchase flow - * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. - * If the purchase is successful, the result data will contain the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response - * codes on failures. - * "INAPP_PURCHASE_DATA" - String in JSON format similar to - * '{"orderId":"12999763169054705758.1371079406387615", - * "packageName":"com.example.app", - * "productId":"exampleSku", - * "purchaseTime":1345678900000, - * "purchaseToken" : "122333444455555", - * "developerPayload":"example developer payload" }' - * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that - * was signed with the private key of the developer - */ - Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type, - String developerPayload); - - /** - * Returns the current SKUs owned by the user of the type and package name specified along with - * purchase information and a signature of the data to be validated. - * This will return all SKUs that have been purchased in V3 and managed items purchased using - * V1 and V2 that have not been consumed. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param type of the in-app items being requested ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param continuationToken to be set as null for the first call, if the number of owned - * skus are too many, a continuationToken is returned in the response bundle. - * This method can be called again with the continuation token to get the next set of - * owned skus. - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - on failures. - * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs - * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information - * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures - * of the purchase information - * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the - * next set of in-app purchases. Only set if the - * user has more owned skus than the current list. - */ - Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken); - - /** - * Consume the last purchase of the given SKU. This will result in this item being removed - * from all subsequent responses to getPurchases() and allow re-purchase of this item. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param purchaseToken token in the purchase information JSON that identifies the purchase - * to be consumed - * @return RESULT_OK(0) if consumption succeeded, appropriate response codes on failures. - */ - int consumePurchase(int apiVersion, String packageName, String purchaseToken); - - /** - * This API is currently under development. - */ - int stub(int apiVersion, String packageName, String type); - - /** - * Returns a pending intent to launch the purchase flow for upgrading or downgrading a - * subscription. The existing owned SKU(s) should be provided along with the new SKU that - * the user is upgrading or downgrading to. - * @param apiVersion billing API version that the app is using, must be 5 or later - * @param packageName package name of the calling app - * @param oldSkus the SKU(s) that the user is upgrading or downgrading from, - * if null or empty this method will behave like {@link #getBuyIntent} - * @param newSku the SKU that the user is upgrading or downgrading to - * @param type of the item being purchased, currently must be "subs" - * @param developerPayload optional argument to be sent back with the purchase information - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "BUY_INTENT" - PendingIntent to start the purchase flow - * - * The Pending intent should be launched with startIntentSenderForResult. When purchase flow - * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. - * If the purchase is successful, the result data will contain the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response - * codes on failures. - * "INAPP_PURCHASE_DATA" - String in JSON format similar to - * '{"orderId":"12999763169054705758.1371079406387615", - * "packageName":"com.example.app", - * "productId":"exampleSku", - * "purchaseTime":1345678900000, - * "purchaseToken" : "122333444455555", - * "developerPayload":"example developer payload" }' - * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that - * was signed with the private key of the developer - */ - Bundle getBuyIntentToReplaceSkus(int apiVersion, String packageName, - in List<String> oldSkus, String newSku, String type, String developerPayload); - - /** - * Returns a pending intent to launch the purchase flow for an in-app item. This method is - * a variant of the {@link #getBuyIntent} method and takes an additional {@code extraParams} - * parameter. This parameter is a Bundle of optional keys and values that affect the - * operation of the method. - * @param apiVersion billing API version that the app is using, must be 6 or later - * @param packageName package name of the calling app - * @param sku the SKU of the in-app item as published in the developer console - * @param type of the in-app item being purchased ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param developerPayload optional argument to be sent back with the purchase information - * @extraParams a Bundle with the following optional keys: - * "skusToReplace" - List<String> - an optional list of SKUs that the user is - * upgrading or downgrading from. - * Pass this field if the purchase is upgrading or downgrading - * existing subscriptions. - * The specified SKUs are replaced with the SKUs that the user is - * purchasing. Google Play replaces the specified SKUs at the start of - * the next billing cycle. - * "replaceSkusProration" - Boolean - whether the user should be credited for any unused - * subscription time on the SKUs they are upgrading or downgrading. - * If you set this field to true, Google Play swaps out the old SKUs - * and credits the user with the unused value of their subscription - * time on a pro-rated basis. - * Google Play applies this credit to the new subscription, and does - * not begin billing the user for the new subscription until after - * the credit is used up. - * If you set this field to false, the user does not receive credit for - * any unused subscription time and the recurrence date does not - * change. - * Default value is true. Ignored if you do not pass skusToReplace. - * "accountId" - String - an optional obfuscated string that is uniquely - * associated with the user's account in your app. - * If you pass this value, Google Play can use it to detect irregular - * activity, such as many devices making purchases on the same - * account in a short period of time. - * Do not use the developer ID or the user's Google ID for this field. - * In addition, this field should not contain the user's ID in - * cleartext. - * We recommend that you use a one-way hash to generate a string from - * the user's ID, and store the hashed string in this field. - * "vr" - Boolean - an optional flag indicating whether the returned intent - * should start a VR purchase flow. The apiVersion must also be 7 or - * later to use this flag. - */ - Bundle getBuyIntentExtraParams(int apiVersion, String packageName, String sku, - String type, String developerPayload, in Bundle extraParams); - - /** - * Returns the most recent purchase made by the user for each SKU, even if that purchase is - * expired, canceled, or consumed. - * @param apiVersion billing API version that the app is using, must be 6 or later - * @param packageName package name of the calling app - * @param type of the in-app items being requested ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param continuationToken to be set as null for the first call, if the number of owned - * skus is too large, a continuationToken is returned in the response bundle. - * This method can be called again with the continuation token to get the next set of - * owned skus. - * @param extraParams a Bundle with extra params that would be appended into http request - * query string. Not used at this moment. Reserved for future functionality. - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value: RESULT_OK(0) if success, - * {@link IabHelper#BILLING_RESPONSE_RESULT_*} response codes on failures. - * - * "INAPP_PURCHASE_ITEM_LIST" - ArrayList<String> containing the list of SKUs - * "INAPP_PURCHASE_DATA_LIST" - ArrayList<String> containing the purchase information - * "INAPP_DATA_SIGNATURE_LIST"- ArrayList<String> containing the signatures - * of the purchase information - * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the - * next set of in-app purchases. Only set if the - * user has more owned skus than the current list. - */ - Bundle getPurchaseHistory(int apiVersion, String packageName, String type, - String continuationToken, in Bundle extraParams); - - /** - * This method is a variant of {@link #isBillingSupported}} that takes an additional - * {@code extraParams} parameter. - * @param apiVersion billing API version that the app is using, must be 7 or later - * @param packageName package name of the calling app - * @param type of the in-app item being purchased ("inapp" for one-time purchases and "subs" - * for subscriptions) - * @param extraParams a Bundle with the following optional keys: - * "vr" - Boolean - an optional flag to indicate whether {link #getBuyIntentExtraParams} - * supports returning a VR purchase flow. - * @return RESULT_OK(0) on success and appropriate response code on failures. - */ - int isBillingSupportedExtraParams(int apiVersion, String packageName, String type, - in Bundle extraParams); -} diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index 13a14422ed..6fc9a11a08 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -1,7 +1,10 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' dependencies { - implementation "com.android.support:support-core-utils:28.0.0" + implementation libraries.supportCoreUtils + implementation libraries.kotlinStdLib + implementation libraries.v4Support } def pathToRootDir = "../../../../" @@ -9,11 +12,19 @@ def pathToRootDir = "../../../../" android { compileSdkVersion versions.compileSdk buildToolsVersion versions.buildTools - useLibrary 'org.apache.http.legacy' + + ndkVersion versions.ndkVersion defaultConfig { minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk + + manifestPlaceholders = [godotLibraryVersion: getGodotLibraryVersion()] + } + + compileOptions { + sourceCompatibility versions.javaVersion + targetCompatibility versions.javaVersion } lintOptions { @@ -24,6 +35,9 @@ android { packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' + + // Should be uncommented for development purpose within Android Studio + // doNotStrip '**/*.so' } sourceSets { @@ -45,16 +59,7 @@ android { def buildType = variant.buildType.name.capitalize() - def taskPrefix = "" - if (project.path != ":") { - taskPrefix = project.path + ":" - } - - // Disable the externalNativeBuild* task as it would cause build failures since the cmake build - // files is only setup for editing support. - gradle.startParameter.excludedTaskNames += taskPrefix + "externalNativeBuild" + buildType - - def releaseTarget = supportedTargets[buildType.toLowerCase()] + def releaseTarget = buildType.toLowerCase() if (releaseTarget == null || releaseTarget == "") { throw new GradleException("Invalid build type: " + buildType) } @@ -63,20 +68,46 @@ android { throw new GradleException("Invalid default abi: " + defaultAbi) } + // Find scons' executable path + File sconsExecutableFile = null + def sconsName = "scons" + def sconsExts = (org.gradle.internal.os.OperatingSystem.current().isWindows() + ? [".bat", ".cmd", ".ps1", ".exe"] + : [""]) + logger.lifecycle("Looking for $sconsName executable path") + for (ext in sconsExts) { + String sconsNameExt = sconsName + ext + logger.lifecycle("Checking $sconsNameExt") + + sconsExecutableFile = org.gradle.internal.os.OperatingSystem.current().findInPath(sconsNameExt) + if (sconsExecutableFile != null) { + // We're done! + break + } + + // Check all the options in path + List<File> allOptions = org.gradle.internal.os.OperatingSystem.current().findAllInPath(sconsNameExt) + if (!allOptions.isEmpty()) { + // Pick the first option and we're done! + sconsExecutableFile = allOptions.get(0) + break + } + } + + if (sconsExecutableFile == null) { + throw new GradleException("Unable to find executable path for the '$sconsName' command.") + } else { + logger.lifecycle("Found executable path for $sconsName: ${sconsExecutableFile.absolutePath}") + } + // Creating gradle task to generate the native libraries for the default abi. def taskName = getSconsTaskName(buildType) tasks.create(name: taskName, type: Exec) { - executable "scons" + sconsExt + executable sconsExecutableFile.absolutePath args "--directory=${pathToRootDir}", "platform=android", "target=${releaseTarget}", "android_arch=${defaultAbi}", "-j" + Runtime.runtime.availableProcessors() } // Schedule the tasks so the generated libs are present before the aar file is packaged. tasks["merge${buildType}JniLibFolders"].dependsOn taskName } - - externalNativeBuild { - cmake { - path "CMakeLists.txt" - } - } } diff --git a/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png Binary files differdeleted file mode 100644 index f849d8e90d..0000000000 --- a/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png +++ /dev/null diff --git a/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png Binary files differdeleted file mode 100644 index 1dfb28b33a..0000000000 --- a/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png +++ /dev/null diff --git a/platform/android/java/lib/res/drawable-xhdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-xhdpi/notify_panel_notification_icon_bg.png Binary files differdeleted file mode 100644 index 372b763ec5..0000000000 --- a/platform/android/java/lib/res/drawable-xhdpi/notify_panel_notification_icon_bg.png +++ /dev/null diff --git a/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png Binary files differdeleted file mode 100644 index 302a972049..0000000000 --- a/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png +++ /dev/null diff --git a/platform/android/java/lib/res/layout/downloading_expansion.xml b/platform/android/java/lib/res/layout/downloading_expansion.xml index 4a9700965f..34c2757598 100644 --- a/platform/android/java/lib/res/layout/downloading_expansion.xml +++ b/platform/android/java/lib/res/layout/downloading_expansion.xml @@ -162,4 +162,4 @@ </LinearLayout> </LinearLayout> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/platform/android/java/lib/res/layout/godot_app_layout.xml b/platform/android/java/lib/res/layout/godot_app_layout.xml new file mode 100644 index 0000000000..386ded1c5d --- /dev/null +++ b/platform/android/java/lib/res/layout/godot_app_layout.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/godot_fragment_container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml b/platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml index fae1faeb60..426e1bd841 100644 --- a/platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml +++ b/platform/android/java/lib/res/layout/status_bar_ongoing_event_progress_bar.xml @@ -105,4 +105,4 @@ </RelativeLayout> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml b/platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml index 1ed4037035..cfdcca2ab5 100644 --- a/platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml +++ b/platform/android/java/lib/res/mipmap-anydpi-v26/icon.xml @@ -2,4 +2,4 @@ <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@mipmap/icon_background"/> <foreground android:drawable="@mipmap/icon_foreground"/> -</adaptive-icon>
\ No newline at end of file +</adaptive-icon> diff --git a/platform/android/java/lib/res/values-fa/strings.xml b/platform/android/java/lib/res/values-fa/strings.xml index f1e29013c4..60b01accf1 100644 --- a/platform/android/java/lib/res/values-fa/strings.xml +++ b/platform/android/java/lib/res/values-fa/strings.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="godot_project_name_string">godot-project-name-fa</string> <string name="text_paused_cellular">آیا می خواهید بر روی اتصال داده همراه دانلود را شروع کنید؟ بر اساس نوع سطح داده شما این ممکن است برای شما هزینه مالی داشته باشد.</string> <string name="text_paused_cellular_2">اگر نمی خواهید بر روی اتصال داده همراه دانلود را شروع کنید ، دانلود به صورت خودکار در زمان دسترسی به وای-فای شروع می شود.</string> <string name="text_button_resume_cellular">ادامه دانلود</string> diff --git a/platform/android/java/lib/res/values-in/strings.xml b/platform/android/java/lib/res/values-in/strings.xml deleted file mode 100644 index 9e9a8b0c03..0000000000 --- a/platform/android/java/lib/res/values-in/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <string name="godot_project_name_string">godot-project-name-id</string> -</resources>
\ No newline at end of file diff --git a/platform/android/java/lib/res/values-iw/strings.xml b/platform/android/java/lib/res/values-iw/strings.xml deleted file mode 100644 index f52ede2085..0000000000 --- a/platform/android/java/lib/res/values-iw/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <string name="godot_project_name_string">godot-project-name-he</string> -</resources>
\ No newline at end of file diff --git a/platform/android/java/lib/res/values-ko/strings.xml b/platform/android/java/lib/res/values-ko/strings.xml index fab0bdd753..7b62345977 100644 --- a/platform/android/java/lib/res/values-ko/strings.xml +++ b/platform/android/java/lib/res/values-ko/strings.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <string name="godot_project_name_string">godot-project-name-ko</string> <string name="text_paused_cellular">모바일 네트워크를 사용하여 다운로드 하시겠습니까? 남은 데이터 사용량에 따라, 요금이 부과될 수 있습니다.</string> <string name="text_paused_cellular_2">모바일 네트워크를 사용하여 다운로드 하지 않을 경우, 와이파이 연결이 가능할 때 자동적으로 다운로드가 이루어집니다.</string> <string name="text_button_resume_cellular">다운로드 계속하기</string> @@ -52,4 +51,4 @@ <string name="kilobytes_per_second">%1$s KB/s</string> <string name="time_remaining">남은 시간: %1$s</string> <string name="time_remaining_notification">%1$s 남음</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values/dimens.xml b/platform/android/java/lib/res/values/dimens.xml new file mode 100644 index 0000000000..9034dbbcc1 --- /dev/null +++ b/platform/android/java/lib/res/values/dimens.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <dimen name="text_edit_height">48dp</dimen> +</resources> diff --git a/platform/android/java/lib/res/values/strings.xml b/platform/android/java/lib/res/values/strings.xml index a1b81a6186..590b066d8a 100644 --- a/platform/android/java/lib/res/values/strings.xml +++ b/platform/android/java/lib/res/values/strings.xml @@ -52,4 +52,4 @@ <string name="kilobytes_per_second">%1$s KB/s</string> <string name="time_remaining">Time remaining: %1$s</string> <string name="time_remaining_notification">%1$s left</string> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/res/values/styles.xml b/platform/android/java/lib/res/values/styles.xml index a442f61e7e..b798373bc6 100644 --- a/platform/android/java/lib/res/values/styles.xml +++ b/platform/android/java/lib/res/values/styles.xml @@ -22,4 +22,4 @@ <item name="android:background">@android:color/background_dark</item> </style> -</resources>
\ No newline at end of file +</resources> diff --git a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Constants.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Constants.java index 1dcc370d83..0700b78a28 100644 --- a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Constants.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Constants.java @@ -233,4 +233,4 @@ public class Constants { */ public static final long ACTIVE_THREAD_WATCHDOG = 5*1000; -}
\ No newline at end of file +} diff --git a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java index 0abaf2e052..d481c22204 100644 --- a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java @@ -29,9 +29,9 @@ import com.google.android.vending.expansion.downloader.IDownloaderClient; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; -import android.os.Build; import android.os.Messenger; -import android.support.v4.app.NotificationCompat; + +import androidx.core.app.NotificationCompat; /** * This class handles displaying the notification associated with the download diff --git a/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java b/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java index 594cae774b..0572cf3589 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Dictionary.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -34,16 +34,13 @@ import java.util.HashMap; import java.util.Set; public class Dictionary extends HashMap<String, Object> { - protected String[] keys_cache; public String[] get_keys() { - String[] ret = new String[size()]; int i = 0; Set<String> keys = keySet(); for (String key : keys) { - ret[i] = key; i++; }; @@ -52,12 +49,10 @@ public class Dictionary extends HashMap<String, Object> { }; public Object[] get_values() { - Object[] ret = new Object[size()]; int i = 0; Set<String> keys = keySet(); for (String key : keys) { - ret[i] = get(key); i++; }; @@ -70,7 +65,6 @@ public class Dictionary extends HashMap<String, Object> { }; public void set_values(Object[] vals) { - int i = 0; for (String key : keys_cache) { put(key, vals[i]); diff --git a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java new file mode 100644 index 0000000000..4e67402c63 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java @@ -0,0 +1,99 @@ +/*************************************************************************/ +/* FullScreenGodotApp.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot; + +import android.content.Intent; +import android.os.Bundle; +import android.view.KeyEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; + +/** + * Base activity for Android apps intending to use Godot as the primary and only screen. + * + * It's also a reference implementation for how to setup and use the {@link Godot} fragment + * within an Android app. + */ +public abstract class FullScreenGodotApp extends FragmentActivity { + @Nullable + private Godot godotFragment; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.godot_app_layout); + godotFragment = initGodotInstance(); + if (godotFragment == null) { + throw new IllegalStateException("Godot instance must be non-null."); + } + + getSupportFragmentManager().beginTransaction().replace(R.id.godot_fragment_container, godotFragment).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss(); + } + + @Override + public void onNewIntent(Intent intent) { + if (godotFragment != null) { + godotFragment.onNewIntent(intent); + } + } + + @Override + public void onBackPressed() { + if (godotFragment != null) { + godotFragment.onBackPressed(); + } else { + super.onBackPressed(); + } + } + + @Override + public boolean onKeyMultiple(final int inKeyCode, int repeatCount, KeyEvent event) { + if (godotFragment != null && godotFragment.onKeyMultiple(inKeyCode, repeatCount, event)) { + return true; + } + return super.onKeyMultiple(inKeyCode, repeatCount, event); + } + + /** + * Used to initialize the Godot fragment instance in {@link FullScreenGodotApp#onCreate(Bundle)}. + */ + @NonNull + protected Godot initGodotInstance() { + return new Godot(); + } + + @Nullable + protected final Godot getGodotFragment() { + return godotFragment; + } +} 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 2f6a93fbb1..0891904dff 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,6 +30,16 @@ package org.godotengine.godot; +import static android.content.Context.MODE_PRIVATE; +import static android.content.Context.WINDOW_SERVICE; + +import org.godotengine.godot.input.GodotEditText; +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; + import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityManager; @@ -55,16 +65,14 @@ import android.hardware.SensorManager; import android.os.Build; import android.os.Bundle; import android.os.Environment; -import android.os.Handler; -import android.os.Looper; import android.os.Messenger; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings.Secure; -import android.support.annotation.Keep; -import android.support.annotation.Nullable; import android.view.Display; +import android.view.InputDevice; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; import android.view.View; @@ -77,6 +85,12 @@ import android.widget.Button; import android.widget.FrameLayout; import android.widget.ProgressBar; import android.widget.TextView; + +import androidx.annotation.CallSuper; +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + import com.google.android.vending.expansion.downloader.DownloadProgressInfo; import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller; @@ -84,25 +98,16 @@ import com.google.android.vending.expansion.downloader.Helpers; import com.google.android.vending.expansion.downloader.IDownloaderClient; import com.google.android.vending.expansion.downloader.IDownloaderService; 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.utils.GodotNetUtils; -import org.godotengine.godot.utils.PermissionsUtil; -import org.godotengine.godot.xr.XRMode; - -public abstract class Godot extends Activity implements SensorEventListener, IDownloaderClient { - static final int MAX_SINGLETONS = 64; +public class Godot extends Fragment implements SensorEventListener, IDownloaderClient { private IStub mDownloaderClientStub; private TextView mStatusText; private TextView mProgressFraction; @@ -126,12 +131,10 @@ 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; - @Override public void onNewIntent(Intent intent) { mCurrentIntent = intent; } @@ -149,92 +152,15 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo private void setButtonPausedState(boolean paused) { mStatePaused = paused; - int stringResourceID = paused ? R.string.text_button_resume : - R.string.text_button_pause; + int stringResourceID = paused ? R.string.text_button_resume : R.string.text_button_pause; 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; - public GodotView mView; + private ViewGroup containerLayout; + public GodotRenderView mRenderView; private boolean godot_initialized = false; private SensorManager mSensorManager; @@ -246,35 +172,27 @@ 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); } public ResultCallback result_callback; - private PaymentsManager mPaymentsManager = null; - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == PaymentsManager.REQUEST_CODE_FOR_PURCHASE) { - mPaymentsManager.processPurchaseResponse(resultCode, data); - } else if (result_callback != null) { + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (result_callback != null) { result_callback.callback(requestCode, resultCode, data); 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,63 +201,78 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo }; /** + * Invoked on the render thread when the Godot main loop has started. + */ + @CallSuper + protected void onGodotMainLoopStarted() { + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGodotMainLoopStarted(); + } + } + + /** * Used by the native code (java_godot_lib_jni.cpp) to complete initialization of the GLSurfaceView view and renderer. */ @Keep private void onVideoInit() { - boolean use_gl3 = getGLESVersionCode() >= 0x00030000; - - final FrameLayout layout = new FrameLayout(this); - layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - setContentView(layout); + final Activity activity = getActivity(); + containerLayout = new FrameLayout(activity); + containerLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); // GodotEditText layout - GodotEditText edittext = new GodotEditText(this); - edittext.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + GodotEditText editText = new GodotEditText(activity); + editText.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, + (int)getResources().getDimension(R.dimen.text_edit_height))); // ...add to FrameLayout - layout.addView(edittext); + containerLayout.addView(editText); - mView = new GodotView(this, xrMode, use_gl3, use_32_bits, use_debug_opengl); - layout.addView(mView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - edittext.setView(mView); - io.setEdit(edittext); + GodotLib.setup(command_line); + + final String videoDriver = GodotLib.getGlobal("rendering/quality/driver/driver_name"); + if (videoDriver.equals("Vulkan")) { + mRenderView = new GodotVulkanRenderView(activity, this); + } else { + mRenderView = new GodotGLRenderView(activity, this, xrMode, use_32_bits, + use_debug_opengl); + } - final Godot godot = this; - mView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + View view = mRenderView.getView(); + containerLayout.addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + editText.setView(mRenderView); + io.setEdit(editText); + + view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { Point fullSize = new Point(); - godot.getWindowManager().getDefaultDisplay().getSize(fullSize); + activity.getWindowManager().getDefaultDisplay().getSize(fullSize); Rect gameSize = new Rect(); - godot.mView.getWindowVisibleDisplayFrame(gameSize); + mRenderView.getView().getWindowVisibleDisplayFrame(gameSize); final int keyboardHeight = fullSize.y - gameSize.bottom; GodotLib.setVirtualKeyboardHeight(keyboardHeight); } }); - final String[] current_command_line = command_line; - mView.queueEvent(new Runnable() { + mRenderView.queueOnRenderThread(new Runnable() { @Override public void run() { - GodotLib.setup(current_command_line); - setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))); + // Must occur after GodotLib.setup has completed. + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onRegisterPluginWithGodotNative(); + } - // 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); - } - } - } - }); + setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))); } }); + + // Include the returned non-null views in the Godot view hierarchy. + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + View pluginView = plugin.onMainCreate(activity); + if (pluginView != null) { + containerLayout.addView(pluginView); + } + } } public void setKeepScreenOn(final boolean p_enabled) { @@ -347,9 +280,9 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo @Override public void run() { if (p_enabled) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } else { - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } }); @@ -363,7 +296,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo @Keep private void vibrate(int durationMs) { if (requestPermission("VIBRATE")) { - Vibrator v = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE); + Vibrator v = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE); if (v != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { v.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE)); @@ -387,13 +320,16 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo // Using instrumentation is a way of making the whole app process restart, because Android // will kill any process of the same package which was already running. // - Bundle args = new Bundle(); - args.putParcelable("intent", mCurrentIntent); - startInstrumentation(new ComponentName(this, GodotInstrumentation.class), null, args); + final Activity activity = getActivity(); + if (activity != null) { + Bundle args = new Bundle(); + args.putParcelable("intent", mCurrentIntent); + activity.startInstrumentation(new ComponentName(activity, GodotInstrumentation.class), null, args); + } } public void alert(final String message, final String title) { - final Activity activity = this; + final Activity activity = getActivity(); runOnUiThread(new Runnable() { @Override public void run() { @@ -413,15 +349,16 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo } public int getGLESVersionCode() { - ActivityManager am = (ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager am = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE); ConfigurationInfo deviceInfo = am.getDeviceConfigurationInfo(); return deviceInfo.reqGlEsVersion; } - private String[] getCommandLine() { + @CallSuper + protected String[] getCommandLine() { InputStream is; try { - is = getAssets().open("_cl_"); + is = getActivity().getAssets().open("_cl_"); byte[] len = new byte[4]; int r = is.read(len); if (r < 4) { @@ -433,7 +370,6 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo for (int i = 0; i < argc; i++) { r = is.read(len); if (r < 4) { - return new String[0]; } int strlen = ((int)(len[3] & 0xFF) << 24) | ((int)(len[2] & 0xFF) << 16) | ((int)(len[1] & 0xFF) << 8) | ((int)(len[0] & 0xFF)); @@ -466,7 +402,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo */ @Keep private Surface getSurface() { - return mView.getHolder().getSurface(); + return mRenderView.getView().getHolder().getSurface(); } /** @@ -481,9 +417,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo String expansion_pack_path; private void initializeGodot() { - if (expansion_pack_path != null) { - String[] new_cmdline; int cll = 0; if (command_line != null) { @@ -501,11 +435,12 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo command_line = new_cmdline; } - io = new GodotIO(this); - io.unique_id = Secure.getString(getContentResolver(), Secure.ANDROID_ID); + final Activity activity = getActivity(); + io = new GodotIO(activity); + io.unique_id = Secure.getString(activity.getContentResolver(), Secure.ANDROID_ID); GodotLib.io = io; - netUtils = new GodotNetUtils(this); - mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); + netUtils = new GodotNetUtils(activity); + mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); mGravity = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); @@ -515,12 +450,10 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME); - GodotLib.initialize(this, getAssets(), use_apk_expansion); + GodotLib.initialize(activity, this, activity.getAssets(), use_apk_expansion); result_callback = null; - mPaymentsManager = PaymentsManager.createManager(this).initService(); - godot_initialized = true; } @@ -531,157 +464,162 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo } @Override - protected void onCreate(Bundle icicle) { - + public void onCreate(Bundle icicle) { super.onCreate(icicle); - Window window = getWindow(); + + final Activity activity = getActivity(); + Window window = activity.getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); - mClipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); + mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE); + pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this); //check for apk expansion API - if (true) { - boolean md5mismatch = false; - command_line = getCommandLine(); - String main_pack_md5 = null; - String main_pack_key = null; - - List<String> new_args = new LinkedList<String>(); - - for (int i = 0; i < command_line.length; i++) { - - boolean has_extra = i < command_line.length - 1; - if (command_line[i].equals(XRMode.REGULAR.cmdLineArg)) { - xrMode = XRMode.REGULAR; - } else if (command_line[i].equals(XRMode.OVR.cmdLineArg)) { - xrMode = XRMode.OVR; - } else if (command_line[i].equals("--use_depth_32")) { - use_32_bits = true; - } else if (command_line[i].equals("--debug_opengl")) { - use_debug_opengl = true; - } else if (command_line[i].equals("--use_immersive")) { - use_immersive = true; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+ - window.getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar - View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - - UiChangeListener(); - } - } else if (command_line[i].equals("--use_apk_expansion")) { - use_apk_expansion = true; - } else if (has_extra && command_line[i].equals("--apk_expansion_md5")) { - main_pack_md5 = command_line[i + 1]; - i++; - } else if (has_extra && command_line[i].equals("--apk_expansion_key")) { - main_pack_key = command_line[i + 1]; - SharedPreferences prefs = getSharedPreferences("app_data_keys", MODE_PRIVATE); - Editor editor = prefs.edit(); - editor.putString("store_public_key", main_pack_key); - - editor.apply(); - i++; - } else if (command_line[i].trim().length() != 0) { - new_args.add(command_line[i]); + boolean md5mismatch = false; + command_line = getCommandLine(); + String main_pack_md5 = null; + String main_pack_key = null; + + List<String> new_args = new LinkedList<String>(); + + for (int i = 0; i < command_line.length; i++) { + boolean has_extra = i < command_line.length - 1; + if (command_line[i].equals(XRMode.REGULAR.cmdLineArg)) { + xrMode = XRMode.REGULAR; + } else if (command_line[i].equals(XRMode.OVR.cmdLineArg)) { + xrMode = XRMode.OVR; + } else if (command_line[i].equals("--use_depth_32")) { + use_32_bits = true; + } else if (command_line[i].equals("--debug_opengl")) { + use_debug_opengl = true; + } else if (command_line[i].equals("--use_immersive")) { + use_immersive = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+ + window.getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar + View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + + UiChangeListener(); } + } else if (command_line[i].equals("--use_apk_expansion")) { + use_apk_expansion = true; + } else if (has_extra && command_line[i].equals("--apk_expansion_md5")) { + main_pack_md5 = command_line[i + 1]; + i++; + } else if (has_extra && command_line[i].equals("--apk_expansion_key")) { + main_pack_key = command_line[i + 1]; + SharedPreferences prefs = activity.getSharedPreferences("app_data_keys", + MODE_PRIVATE); + Editor editor = prefs.edit(); + editor.putString("store_public_key", main_pack_key); + + editor.apply(); + i++; + } else if (command_line[i].trim().length() != 0) { + new_args.add(command_line[i]); } + } - if (new_args.isEmpty()) { - command_line = null; - } else { - - command_line = new_args.toArray(new String[new_args.size()]); + if (new_args.isEmpty()) { + command_line = null; + } else { + command_line = new_args.toArray(new String[new_args.size()]); + } + if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) { + //check that environment is ok! + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + //show popup and die } - if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) { - //check that environment is ok! - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - //show popup and die - } - - // Build the full path to the app's expansion files - try { - expansion_pack_path = Helpers.getSaveFilePath(getApplicationContext()); - expansion_pack_path += "/main." + getPackageManager().getPackageInfo(getPackageName(), 0).versionCode + "." + this.getPackageName() + ".obb"; - } catch (Exception e) { - e.printStackTrace(); - } - File f = new File(expansion_pack_path); + // Build the full path to the app's expansion files + try { + expansion_pack_path = Helpers.getSaveFilePath(getContext()); + expansion_pack_path += "/main." + activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0).versionCode + "." + activity.getPackageName() + ".obb"; + } catch (Exception e) { + e.printStackTrace(); + } - boolean pack_valid = true; + File f = new File(expansion_pack_path); - if (!f.exists()) { + boolean pack_valid = true; - pack_valid = false; + if (!f.exists()) { + pack_valid = false; - } else if (obbIsCorrupted(expansion_pack_path, main_pack_md5)) { - pack_valid = false; - try { - f.delete(); - } catch (Exception e) { - } + } else if (obbIsCorrupted(expansion_pack_path, main_pack_md5)) { + pack_valid = false; + try { + f.delete(); + } catch (Exception e) { } + } - if (!pack_valid) { - - Intent notifierIntent = new Intent(this, this.getClass()); - notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_CLEAR_TOP); + if (!pack_valid) { + Intent notifierIntent = new Intent(activity, activity.getClass()); + notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_CLEAR_TOP); - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, - notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0, + notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); - int startResult; - try { - startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired( - getApplicationContext(), - pendingIntent, + int startResult; + try { + startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired( + getContext(), + pendingIntent, + GodotDownloaderService.class); + + if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { + // This is where you do set up to display the download + // progress (next step in onCreateView) + mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, GodotDownloaderService.class); - if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { - // This is where you do set up to display the download - // progress (next step) - mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, - GodotDownloaderService.class); - - setContentView(R.layout.downloading_expansion); - mPB = (ProgressBar)findViewById(R.id.progressBar); - mStatusText = (TextView)findViewById(R.id.statusText); - mProgressFraction = (TextView)findViewById(R.id.progressAsFraction); - mProgressPercent = (TextView)findViewById(R.id.progressAsPercentage); - mAverageSpeed = (TextView)findViewById(R.id.progressAverageSpeed); - mTimeRemaining = (TextView)findViewById(R.id.progressTimeRemaining); - mDashboard = findViewById(R.id.downloaderDashboard); - mCellMessage = findViewById(R.id.approveCellular); - mPauseButton = (Button)findViewById(R.id.pauseButton); - mWiFiSettingsButton = (Button)findViewById(R.id.wifiSettingsButton); - - return; - } - } catch (NameNotFoundException e) { - // TODO Auto-generated catch block + return; } + } catch (NameNotFoundException e) { + // TODO Auto-generated catch block } } } - mCurrentIntent = getIntent(); + mCurrentIntent = activity.getIntent(); initializeGodot(); } @Override - protected void onDestroy() { + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) { + if (mDownloaderClientStub != null) { + View downloadingExpansionView = + inflater.inflate(R.layout.downloading_expansion, container, false); + mPB = (ProgressBar)downloadingExpansionView.findViewById(R.id.progressBar); + mStatusText = (TextView)downloadingExpansionView.findViewById(R.id.statusText); + mProgressFraction = (TextView)downloadingExpansionView.findViewById(R.id.progressAsFraction); + mProgressPercent = (TextView)downloadingExpansionView.findViewById(R.id.progressAsPercentage); + mAverageSpeed = (TextView)downloadingExpansionView.findViewById(R.id.progressAverageSpeed); + mTimeRemaining = (TextView)downloadingExpansionView.findViewById(R.id.progressTimeRemaining); + mDashboard = downloadingExpansionView.findViewById(R.id.downloaderDashboard); + mCellMessage = downloadingExpansionView.findViewById(R.id.approveCellular); + mPauseButton = (Button)downloadingExpansionView.findViewById(R.id.pauseButton); + mWiFiSettingsButton = (Button)downloadingExpansionView.findViewById(R.id.wifiSettingsButton); + + return downloadingExpansionView; + } + + return containerLayout; + } - if (mPaymentsManager != null) mPaymentsManager.destroy(); - for (int i = 0; i < singleton_count; i++) { - singletons[i].onMainDestroy(); + @Override + public void onDestroy() { + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onMainDestroy(); } - GodotLib.ondestroy(this); + GodotLib.ondestroy(); super.onDestroy(); @@ -691,27 +629,26 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo } @Override - protected void onPause() { + public void onPause() { super.onPause(); activityResumed = false; if (!godot_initialized) { if (null != mDownloaderClientStub) { - mDownloaderClientStub.disconnect(this); + mDownloaderClientStub.disconnect(getActivity()); } return; } - mView.onPause(); + mRenderView.onActivityPaused(); mSensorManager.unregisterListener(this); - for (int i = 0; i < singleton_count; i++) { - singletons[i].onMainPause(); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onMainPause(); } } public String getClipboard() { - String copiedText = ""; if (mClipboard.getPrimaryClip() != null) { @@ -723,23 +660,22 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo } public void setClipboard(String p_text) { - ClipData clip = ClipData.newPlainText("myLabel", p_text); mClipboard.setPrimaryClip(clip); } @Override - protected void onResume() { + public void onResume() { super.onResume(); activityResumed = true; if (!godot_initialized) { if (null != mDownloaderClientStub) { - mDownloaderClientStub.connect(this); + mDownloaderClientStub.connect(getActivity()); } return; } - mView.onResume(); + mRenderView.onActivityResumed(); mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); mSensorManager.registerListener(this, mGravity, SensorManager.SENSOR_DELAY_GAME); @@ -747,7 +683,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME); if (use_immersive && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+ - Window window = getWindow(); + Window window = getActivity().getWindow(); window.getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | @@ -757,14 +693,13 @@ 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(); } } public void UiChangeListener() { - final View decorView = getWindow().getDecorView(); + final View decorView = getActivity().getWindow().getDecorView(); decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() { @Override public void onSystemUiVisibilityChange(int visibility) { @@ -785,7 +720,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo @Override public void onSensorChanged(SensorEvent event) { - Display display = ((WindowManager)getSystemService(WINDOW_SERVICE)).getDefaultDisplay(); + Display display = + ((WindowManager)getActivity().getSystemService(WINDOW_SERVICE)).getDefaultDisplay(); int displayRotation = display.getRotation(); float[] adjustedValues = new float[3]; @@ -806,8 +742,8 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo final float z = adjustedValues[2]; final int typeOfSensor = event.sensor.getType(); - if (mView != null) { - mView.queueEvent(new Runnable() { + if (mRenderView != null) { + mRenderView.queueOnRenderThread(new Runnable() { @Override public void run() { if (typeOfSensor == Sensor.TYPE_ACCELEROMETER) { @@ -834,9 +770,7 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo /* @Override public boolean dispatchKeyEvent(KeyEvent event) { - if (event.getKeyCode()==KeyEvent.KEYCODE_BACK) { - System.out.printf("** BACK REQUEST!\n"); GodotLib.quit(); @@ -848,18 +782,17 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo } */ - @Override 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; } } - if (shouldQuit && mView != null) { - mView.queueEvent(new Runnable() { + if (shouldQuit && mRenderView != null) { + mRenderView.queueOnRenderThread(new Runnable() { @Override public void run() { GodotLib.back(); @@ -868,14 +801,29 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo } } + /** + * Queue a runnable to be run on the render thread. + * <p> + * This must be called after the render thread has started. + */ + public final void runOnRenderThread(@NonNull Runnable action) { + if (mRenderView != null) { + mRenderView.queueOnRenderThread(action); + } + } + + public final void runOnUiThread(@NonNull Runnable action) { + if (getActivity() != null) { + getActivity().runOnUiThread(action); + } + } + private void forceQuit() { System.exit(0); } private boolean obbIsCorrupted(String f, String main_pack_md5) { - try { - InputStream fis = new FileInputStream(f); // Create MD5 Hash @@ -915,85 +863,26 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo } } - public boolean gotTouchEvent(final MotionEvent event) { - - final int evcount = event.getPointerCount(); - if (evcount == 0) - return true; - - if (mView != null) { - final int[] arr = new int[event.getPointerCount() * 3]; - - for (int i = 0; i < event.getPointerCount(); i++) { - - arr[i * 3 + 0] = (int)event.getPointerId(i); - arr[i * 3 + 1] = (int)event.getX(i); - arr[i * 3 + 2] = (int)event.getY(i); - } - final int pointer_idx = event.getPointerId(event.getActionIndex()); - - //System.out.printf("gaction: %d\n",event.getAction()); - final int action = event.getAction() & MotionEvent.ACTION_MASK; - mView.queueEvent(new Runnable() { - @Override - public void run() { - switch (action) { - case MotionEvent.ACTION_DOWN: { - GodotLib.touch(0, 0, evcount, arr); - //System.out.printf("action down at: %f,%f\n", event.getX(),event.getY()); - } break; - case MotionEvent.ACTION_MOVE: { - GodotLib.touch(1, 0, evcount, arr); - /* - for(int i=0;i<event.getPointerCount();i++) { - System.out.printf("%d - moved to: %f,%f\n",i, event.getX(i),event.getY(i)); - } - */ - } break; - case MotionEvent.ACTION_POINTER_UP: { - GodotLib.touch(4, pointer_idx, evcount, arr); - //System.out.printf("%d - s.up at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx)); - } break; - case MotionEvent.ACTION_POINTER_DOWN: { - GodotLib.touch(3, pointer_idx, evcount, arr); - //System.out.printf("%d - s.down at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx)); - } break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - GodotLib.touch(2, 0, evcount, arr); - /* - for(int i=0;i<event.getPointerCount();i++) { - System.out.printf("%d - up! %f,%f\n",i, event.getX(i),event.getY(i)); - } - */ - } break; - } - } - }); - } - return true; - } - - @Override public boolean onKeyMultiple(final int inKeyCode, int repeatCount, KeyEvent event) { String s = event.getCharacters(); if (s == null || s.length() == 0) - return super.onKeyMultiple(inKeyCode, repeatCount, event); + return false; final char[] cc = s.toCharArray(); int cnt = 0; for (int i = cc.length; --i >= 0; cnt += cc[i] != 0 ? 1 : 0) ; - if (cnt == 0) return super.onKeyMultiple(inKeyCode, repeatCount, event); - mView.queueEvent(new Runnable() { + if (cnt == 0) + return false; + mRenderView.queueOnRenderThread(new Runnable() { // This method will be called on the rendering thread: public void run() { for (int i = 0, n = cc.length; i < n; i++) { int keyCode; if ((keyCode = cc[i]) != 0) { // Simulate key down and up... - GodotLib.key(0, keyCode, true); - GodotLib.key(0, keyCode, false); + GodotLib.key(0, 0, keyCode, true); + GodotLib.key(0, 0, keyCode, false); } } } @@ -1001,20 +890,16 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo return true; } - public PaymentsManager getPaymentsManager() { - return mPaymentsManager; - } - public boolean requestPermission(String p_name) { - return PermissionsUtil.requestPermission(p_name, this); + return PermissionsUtil.requestPermission(p_name, getActivity()); } public boolean requestPermissions() { - return PermissionsUtil.requestManifestPermissions(this); + return PermissionsUtil.requestManifestPermissions(getActivity()); } public String[] getGrantedPermissions() { - return PermissionsUtil.getGrantedPermissions(this); + return PermissionsUtil.getGrantedPermissions(getActivity()); } /** @@ -1111,6 +996,11 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo progress.mOverallTotal)); } public void initInputDevices() { - mView.initInputDevices(); + mRenderView.initInputDevices(); + } + + @Keep + private GodotRenderView getRenderView() { // used by native side to get renderView + return mRenderView; } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java index 1fb242d0bc..9784d51182 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -35,6 +35,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager.NameNotFoundException; import android.util.Log; + import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; /** @@ -45,7 +46,6 @@ import com.google.android.vending.expansion.downloader.DownloaderClientMarshalle * <receiver android:name=".GodotDownloaderAlarmReceiver"/> */ public class GodotDownloaderAlarmReceiver extends BroadcastReceiver { - @Override public void onReceive(Context context, Intent intent) { Log.d("GODOT", "Alarma recivida"); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java index 7e74e8a80d..d33faab641 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotDownloaderService.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -33,6 +33,7 @@ package org.godotengine.godot; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; + import com.google.android.vending.expansion.downloader.impl.DownloaderService; /** diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java index f938583082..63c91561ff 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -1,12 +1,12 @@ /*************************************************************************/ -/* GodotView.java */ +/* GodotGLRenderView.java */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,12 +29,6 @@ /*************************************************************************/ package org.godotengine.godot; -import android.annotation.SuppressLint; -import android.graphics.PixelFormat; -import android.opengl.GLSurfaceView; -import android.view.GestureDetector; -import android.view.KeyEvent; -import android.view.MotionEvent; import org.godotengine.godot.input.GodotGestureHandler; import org.godotengine.godot.input.GodotInputHandler; import org.godotengine.godot.utils.GLUtils; @@ -46,6 +40,15 @@ import org.godotengine.godot.xr.regular.RegularConfigChooser; import org.godotengine.godot.xr.regular.RegularContextFactory; import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser; +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.PixelFormat; +import android.opengl.GLSurfaceView; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.SurfaceView; + /** * A simple GLSurfaceView sub-class that demonstrate how to perform * OpenGL ES 2.0 rendering into a GL Surface. Note the following important @@ -64,38 +67,66 @@ import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser; * that matches it exactly (with regards to red/green/blue/alpha channels * bit depths). Failure to do so would result in an EGL_BAD_MATCH error. */ -public class GodotView extends GLSurfaceView { - - private static String TAG = GodotView.class.getSimpleName(); - - private final Godot activity; +public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView { + private final Godot godot; private final GodotInputHandler inputHandler; private final GestureDetector detector; private final GodotRenderer godotRenderer; - public GodotView(Godot activity, XRMode xrMode, boolean p_use_gl3, boolean p_use_32_bits, boolean p_use_debug_opengl) { - super(activity); - GLUtils.use_gl3 = p_use_gl3; + public GodotGLRenderView(Context context, Godot godot, XRMode xrMode, boolean p_use_32_bits, + boolean p_use_debug_opengl) { + super(context); GLUtils.use_32 = p_use_32_bits; GLUtils.use_debug_opengl = p_use_debug_opengl; - this.activity = activity; + this.godot = godot; this.inputHandler = new GodotInputHandler(this); - this.detector = new GestureDetector(activity, new GodotGestureHandler(this)); + this.detector = new GestureDetector(context, new GodotGestureHandler(this)); this.godotRenderer = new GodotRenderer(); init(xrMode, false, 16, 0); } + @Override + public SurfaceView getView() { + return this; + } + + @Override public void initInputDevices() { this.inputHandler.initInputDevices(); } + @Override + public void queueOnRenderThread(Runnable event) { + queueEvent(event); + } + + @Override + public void onActivityPaused() { + onPause(); + } + + @Override + public void onActivityResumed() { + onResume(); + } + + @Override + public void onBackPressed() { + godot.onBackPressed(); + } + + @Override + public GodotInputHandler getInputHandler() { + return inputHandler; + } + @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); this.detector.onTouchEvent(event); - return activity.gotTouchEvent(event); + return inputHandler.onTouchEvent(event); } @Override @@ -113,12 +144,15 @@ public class GodotView extends GLSurfaceView { return inputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); } - private void init(XRMode xrMode, boolean translucent, int depth, int stencil) { + @Override + public boolean onCapturedPointerEvent(MotionEvent event) { + return inputHandler.onGenericMotionEvent(event); + } + private void init(XRMode xrMode, boolean translucent, int depth, int stencil) { setPreserveEGLContextOnPause(true); setFocusableInTouchMode(true); switch (xrMode) { - case OVR: // Replace the default egl config chooser. setEGLConfigChooser(new OvrConfigChooser()); @@ -154,15 +188,15 @@ public class GodotView extends GLSurfaceView { if (GLUtils.use_32) { setEGLConfigChooser(translucent ? - new RegularFallbackConfigChooser(8, 8, 8, 8, 24, stencil, + new RegularFallbackConfigChooser(8, 8, 8, 8, 24, stencil, new RegularConfigChooser(8, 8, 8, 8, 16, stencil)) : - new RegularFallbackConfigChooser(8, 8, 8, 8, 24, stencil, + new RegularFallbackConfigChooser(8, 8, 8, 8, 24, stencil, new RegularConfigChooser(5, 6, 5, 0, 16, stencil))); } else { setEGLConfigChooser(translucent ? - new RegularConfigChooser(8, 8, 8, 8, 16, stencil) : - new RegularConfigChooser(5, 6, 5, 0, 16, stencil)); + new RegularConfigChooser(8, 8, 8, 8, 16, stencil) : + new RegularConfigChooser(5, 6, 5, 0, 16, stencil)); } break; } @@ -171,10 +205,6 @@ public class GodotView extends GLSurfaceView { setRenderer(godotRenderer); } - public void onBackPressed() { - activity.onBackPressed(); - } - @Override public void onResume() { super.onResume(); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java index 271d508a4d..c7c7c1b40c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,32 +29,36 @@ /*************************************************************************/ package org.godotengine.godot; + +import org.godotengine.godot.input.*; + +import android.app.Activity; import android.content.*; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.AssetManager; +import android.graphics.Point; import android.media.*; import android.net.Uri; import android.os.*; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; +import android.view.Display; +import android.view.DisplayCutout; +import android.view.WindowInsets; + import java.io.IOException; import java.io.InputStream; import java.util.Locale; -import org.godotengine.godot.input.*; -//android.os.Build // Wrapper for native library public class GodotIO { - AssetManager am; - Godot activity; + final Activity activity; GodotEditText edit; - MediaPlayer mediaPlayer; - final int SCREEN_LANDSCAPE = 0; final int SCREEN_PORTRAIT = 1; final int SCREEN_REVERSE_LANDSCAPE = 2; @@ -70,7 +74,6 @@ public class GodotIO { public int last_file_id = 1; class AssetData { - public boolean eof = false; public String path; public InputStream is; @@ -81,7 +84,6 @@ public class GodotIO { SparseArray<AssetData> streams; public int file_open(String path, boolean write) { - //System.out.printf("file_open: Attempt to Open %s\n",path); //Log.v("MyApp", "TRYING TO OPEN FILE: " + path); @@ -94,7 +96,6 @@ public class GodotIO { ad.is = am.open(path); } catch (Exception e) { - //System.out.printf("Exception on file_open: %s\n",path); return -1; } @@ -102,7 +103,6 @@ public class GodotIO { try { ad.len = ad.is.available(); } catch (Exception e) { - System.out.printf("Exception availabling on file_open: %s\n", path); return -1; } @@ -115,7 +115,6 @@ public class GodotIO { return last_file_id; } public int file_get_size(int id) { - if (streams.get(id) == null) { System.out.printf("file_get_size: Invalid file id: %d\n", id); return -1; @@ -124,7 +123,6 @@ public class GodotIO { return streams.get(id).len; } public void file_seek(int id, int bytes) { - if (streams.get(id) == null) { System.out.printf("file_get_size: Invalid file id: %d\n", id); return; @@ -137,7 +135,6 @@ public class GodotIO { bytes = 0; try { - if (bytes > (int)ad.pos) { int todo = bytes - (int)ad.pos; while (todo > 0) { @@ -145,7 +142,6 @@ public class GodotIO { } ad.pos = bytes; } else if (bytes < (int)ad.pos) { - ad.is = am.open(ad.path); ad.pos = bytes; @@ -157,14 +153,12 @@ public class GodotIO { ad.eof = false; } catch (IOException e) { - System.out.printf("Exception on file_seek: %s\n", e); return; } } public int file_tell(int id) { - if (streams.get(id) == null) { System.out.printf("file_read: Can't tell eof for invalid file id: %d\n", id); return 0; @@ -174,7 +168,6 @@ public class GodotIO { return ad.pos; } public boolean file_eof(int id) { - if (streams.get(id) == null) { System.out.printf("file_read: Can't check eof for invalid file id: %d\n", id); return false; @@ -185,7 +178,6 @@ public class GodotIO { } public byte[] file_read(int id, int bytes) { - if (streams.get(id) == null) { System.out.printf("file_read: Can't read invalid file id: %d\n", id); return new byte[0]; @@ -194,13 +186,11 @@ public class GodotIO { AssetData ad = streams.get(id); if (ad.pos + bytes > ad.len) { - bytes = ad.len - ad.pos; ad.eof = true; } if (bytes == 0) { - return new byte[0]; } @@ -209,7 +199,6 @@ public class GodotIO { try { r = ad.is.read(buf1); } catch (IOException e) { - System.out.printf("Exception on file_read: %s\n", e); return new byte[bytes]; } @@ -221,19 +210,16 @@ public class GodotIO { ad.pos += r; if (r < bytes) { - byte[] buf2 = new byte[r]; for (int i = 0; i < r; i++) buf2[i] = buf1[i]; return buf2; } else { - return buf1; } } public void file_close(int id) { - if (streams.get(id) == null) { System.out.printf("file_close: Can't close invalid file id: %d\n", id); return; @@ -247,7 +233,6 @@ public class GodotIO { ///////////////////////// class AssetDir { - public String[] files; public int current; public String path; @@ -258,7 +243,6 @@ public class GodotIO { SparseArray<AssetDir> dirs; public int dir_open(String path) { - AssetDir ad = new AssetDir(); ad.current = 0; ad.path = path; @@ -271,7 +255,6 @@ public class GodotIO { return -1; } } catch (IOException e) { - System.out.printf("Exception on dir_open: %s\n", e); return -1; } @@ -310,7 +293,6 @@ public class GodotIO { } public String dir_next(int id) { - if (dirs.get(id) == null) { System.out.printf("dir_next: invalid dir id: %d\n", id); return ""; @@ -329,7 +311,6 @@ public class GodotIO { } public void dir_close(int id) { - if (dirs.get(id) == null) { System.out.printf("dir_close: invalid dir id: %d\n", id); return; @@ -338,8 +319,7 @@ public class GodotIO { dirs.remove(id); } - GodotIO(Godot p_activity) { - + GodotIO(Activity p_activity) { am = p_activity.getAssets(); activity = p_activity; //streams = new HashMap<Integer, AssetData>(); @@ -430,7 +410,6 @@ public class GodotIO { } public void audioPause(boolean p_pause) { - if (p_pause) mAudioTrack.pause(); else @@ -442,7 +421,6 @@ public class GodotIO { ///////////////////////// public int openURI(String p_uri) { - try { Log.v("MyApp", "TRYING TO OPEN URI: " + p_uri); String path = p_uri; @@ -451,7 +429,6 @@ public class GodotIO { //absolute path to filesystem, prepend file:// path = "file://" + path; if (p_uri.endsWith(".png") || p_uri.endsWith(".jpg") || p_uri.endsWith(".gif") || p_uri.endsWith(".webp")) { - type = "image/*"; } } @@ -467,18 +444,15 @@ public class GodotIO { activity.startActivity(intent); return 0; } catch (ActivityNotFoundException e) { - return 1; } } public String getDataDir() { - return activity.getFilesDir().getAbsolutePath(); } public String getLocale() { - return Locale.getDefault().toString(); } @@ -491,9 +465,31 @@ public class GodotIO { return (int)(metrics.density * 160f); } - public void showKeyboard(String p_existing_text) { + public int[] screenGetUsableRect() { + DisplayMetrics metrics = activity.getResources().getDisplayMetrics(); + Display display = activity.getWindowManager().getDefaultDisplay(); + Point size = new Point(); + display.getRealSize(size); + + int result[] = { 0, 0, size.x, size.y }; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets(); + DisplayCutout cutout = insets.getDisplayCutout(); + if (cutout != null) { + int insetLeft = cutout.getSafeInsetLeft(); + int insetTop = cutout.getSafeInsetTop(); + result[0] = insetLeft; + result[1] = insetTop; + result[2] -= insetLeft + cutout.getSafeInsetRight(); + result[3] -= insetTop + cutout.getSafeInsetBottom(); + } + } + return result; + } + + public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { if (edit != null) - edit.showKeyboard(p_existing_text); + edit.showKeyboard(p_existing_text, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end); //InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE); //inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); @@ -505,9 +501,7 @@ public class GodotIO { }; public void setScreenOrientation(int p_orientation) { - switch (p_orientation) { - case SCREEN_LANDSCAPE: { activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } break; @@ -521,53 +515,23 @@ public class GodotIO { activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT); } break; case SCREEN_SENSOR_LANDSCAPE: { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE); } break; case SCREEN_SENSOR_PORTRAIT: { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT); } break; case SCREEN_SENSOR: { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER); } break; } - }; - - public void setEdit(GodotEditText _edit) { - edit = _edit; } - public void playVideo(String p_path) { - Uri filePath = Uri.parse(p_path); - mediaPlayer = new MediaPlayer(); - - try { - mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - mediaPlayer.setDataSource(activity.getApplicationContext(), filePath); - mediaPlayer.prepare(); - mediaPlayer.start(); - } catch (IOException e) { - System.out.println("IOError while playing video"); - } + public int getScreenOrientation() { + return activity.getRequestedOrientation(); } - public boolean isVideoPlaying() { - if (mediaPlayer != null) { - return mediaPlayer.isPlaying(); - } - return false; - } - - public void pauseVideo() { - if (mediaPlayer != null) { - mediaPlayer.pause(); - } - } - - public void stopVideo() { - if (mediaPlayer != null) { - mediaPlayer.release(); - mediaPlayer = null; - } + public void setEdit(GodotEditText _edit) { + edit = _edit; } public static final int SYSTEM_DIR_DESKTOP = 0; @@ -580,7 +544,6 @@ public class GodotIO { public static final int SYSTEM_DIR_RINGTONES = 7; public String getSystemDir(int idx) { - String what = ""; switch (idx) { case SYSTEM_DIR_DESKTOP: { @@ -625,7 +588,6 @@ public class GodotIO { public static String unique_id = ""; public String getUniqueID() { - return unique_id; } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotInstrumentation.java b/platform/android/java/lib/src/org/godotengine/godot/GodotInstrumentation.java index 965e616ef3..7f5fd8627c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotInstrumentation.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotInstrumentation.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index e0b46673ba..534a50e9ed 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,6 +32,8 @@ package org.godotengine.godot; import android.app.Activity; import android.hardware.SensorEvent; +import android.view.Surface; + import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; @@ -39,7 +41,6 @@ import javax.microedition.khronos.opengles.GL10; * Wrapper for native library */ public class GodotLib { - public static GodotIO io; static { @@ -49,13 +50,13 @@ public class GodotLib { /** * Invoked on the main thread to initialize Godot native layer. */ - public static native void initialize(Godot p_instance, Object p_asset_manager, boolean use_apk_expansion); + public static native void initialize(Activity activity, Godot p_instance, Object p_asset_manager, boolean use_apk_expansion); /** * Invoked on the main thread to clean up Godot native layer. - * @see Activity#onDestroy() + * @see androidx.fragment.app.Fragment#onDestroy() */ - public static native void ondestroy(Godot p_instance); + public static native void ondestroy(); /** * Invoked on the GL thread to complete setup for the Godot native layer logic. @@ -65,18 +66,19 @@ public class GodotLib { /** * Invoked on the GL thread when the underlying Android surface has changed size. - * @param width - * @param height + * @param p_surface + * @param p_width + * @param p_height * @see android.opengl.GLSurfaceView.Renderer#onSurfaceChanged(GL10, int, int) */ - public static native void resize(int width, int height); + public static native void resize(Surface p_surface, int p_width, int p_height); /** - * Invoked on the GL thread when the underlying Android surface is created or recreated. + * Invoked on the render thread when the underlying Android surface is created or recreated. + * @param p_surface * @param p_32_bits - * @see android.opengl.GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig) */ - public static native void newcontext(boolean p_32_bits); + public static native void newcontext(Surface p_surface, boolean p_32_bits); /** * Forward {@link Activity#onBackPressed()} event from the main thread to the GL thread. @@ -92,17 +94,19 @@ public class GodotLib { /** * Forward touch events from the main thread to the GL thread. */ - public static native void touch(int what, int pointer, int howmany, int[] arr); + public static native void touch(int inputDevice, int event, int pointer, int pointerCount, float[] positions); + public static native void touch(int inputDevice, int event, int pointer, int pointerCount, float[] positions, int buttonsMask); + public static native void touch(int inputDevice, int event, int pointer, int pointerCount, float[] positions, int buttonsMask, float verticalFactor, float horizontalFactor); /** * Forward hover events from the main thread to the GL thread. */ - public static native void hover(int type, int x, int y); + public static native void hover(int type, float x, float y); /** * Forward double_tap events from the main thread to the GL thread. */ - public static native void doubletap(int x, int y); + public static native void doubleTap(int buttonMask, int x, int y); /** * Forward scroll events from the main thread to the GL thread. @@ -136,7 +140,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. @@ -159,14 +163,14 @@ public class GodotLib { public static native void joyconnectionchanged(int p_device, boolean p_connected, String p_name); /** - * Invoked when the Android activity resumes. - * @see Activity#onResume() + * Invoked when the Android app resumes. + * @see androidx.fragment.app.Fragment#onResume() */ public static native void focusin(); /** - * Invoked when the Android activity pauses. - * @see Activity#onPause() + * Invoked when the Android app pauses. + * @see androidx.fragment.app.Fragment#onPause() */ public static native void focusout(); @@ -176,22 +180,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 @@ -204,7 +192,7 @@ public class GodotLib { * @param p_method Name of the method to invoke * @param p_params Parameters to use for method invocation */ - public static native void callobject(int p_id, String p_method, Object[] p_params); + public static native void callobject(long p_id, String p_method, Object[] p_params); /** * Invoke method |p_method| on the Godot object specified by |p_id| during idle time. @@ -212,7 +200,7 @@ public class GodotLib { * @param p_method Name of the method to invoke * @param p_params Parameters to use for method invocation */ - public static native void calldeferred(int p_id, String p_method, Object[] p_params); + public static native void calldeferred(long p_id, String p_method, Object[] p_params); /** * Forward the results from a permission request. diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java b/platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java deleted file mode 100644 index 93265d509f..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotPaymentV3.java +++ /dev/null @@ -1,230 +0,0 @@ -/*************************************************************************/ -/* GodotPaymentV3.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot; - -import android.app.Activity; -import android.util.Log; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.godotengine.godot.payments.PaymentsManager; -import org.json.JSONException; -import org.json.JSONObject; - -public class GodotPaymentV3 extends Godot.SingletonBase { - - private Godot activity; - private Integer purchaseCallbackId = 0; - private String accessToken; - private String purchaseValidationUrlPrefix; - private String transactionId; - private PaymentsManager mPaymentManager; - private Dictionary mSkuDetails = new Dictionary(); - - public void purchase(final String sku, final String transactionId) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - mPaymentManager.requestPurchase(sku, transactionId); - } - }); - } - - 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); - } - - public void consumeUnconsumedPurchases() { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - mPaymentManager.consumeUnconsumedPurchases(); - } - }); - } - - private String signature; - - public String getSignature() { - return this.signature; - } - - public void callbackSuccess(String ticket, String signature, String sku) { - GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[] { ticket, signature, sku }); - } - - 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 }); - } - - public void callbackSuccessNoUnconsumedPurchases() { - GodotLib.calldeferred(purchaseCallbackId, "consume_not_required", new Object[] {}); - } - - public void callbackFailConsume(String message) { - GodotLib.calldeferred(purchaseCallbackId, "consume_fail", new Object[] { message }); - } - - public void callbackFail(String message) { - GodotLib.calldeferred(purchaseCallbackId, "purchase_fail", new Object[] { message }); - } - - public void callbackCancel() { - GodotLib.calldeferred(purchaseCallbackId, "purchase_cancel", new Object[] {}); - } - - public void callbackAlreadyOwned(String sku) { - GodotLib.calldeferred(purchaseCallbackId, "purchase_owned", new Object[] { sku }); - } - - public int getPurchaseCallbackId() { - return purchaseCallbackId; - } - - public void setPurchaseCallbackId(int purchaseCallbackId) { - this.purchaseCallbackId = purchaseCallbackId; - } - - public String getPurchaseValidationUrlPrefix() { - return this.purchaseValidationUrlPrefix; - } - - public void setPurchaseValidationUrlPrefix(String url) { - this.purchaseValidationUrlPrefix = url; - } - - public String getAccessToken() { - return accessToken; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - public void setTransactionId(String transactionId) { - this.transactionId = transactionId; - } - - public String getTransactionId() { - return this.transactionId; - } - - // request purchased items are not consumed - public void requestPurchased() { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - mPaymentManager.requestPurchased(); - } - }); - } - - // callback for requestPurchased() - public void callbackPurchased(String receipt, String signature, String sku) { - GodotLib.calldeferred(purchaseCallbackId, "has_purchased", new Object[] { receipt, signature, sku }); - } - - public void callbackDisconnected() { - GodotLib.calldeferred(purchaseCallbackId, "iap_disconnected", new Object[] {}); - } - - public void callbackConnected() { - GodotLib.calldeferred(purchaseCallbackId, "iap_connected", new Object[] {}); - } - - // true if connected, false otherwise - public boolean isConnected() { - return mPaymentManager.isConnected(); - } - - // consume item automatically after purchase. default is true. - public void setAutoConsume(boolean autoConsume) { - mPaymentManager.setAutoConsume(autoConsume); - } - - // consume a specific item - public void consume(String sku) { - mPaymentManager.consume(sku); - } - - // query in app item detail info - public void querySkuDetails(String[] list) { - List<String> nKeys = Arrays.asList(list); - List<String> cKeys = Arrays.asList(mSkuDetails.get_keys()); - ArrayList<String> fKeys = new ArrayList<String>(); - for (String key : nKeys) { - if (!cKeys.contains(key)) { - fKeys.add(key); - } - } - if (fKeys.size() > 0) { - mPaymentManager.querySkuDetails(fKeys.toArray(new String[0])); - } else { - completeSkuDetail(); - } - } - - public void addSkuDetail(String itemJson) { - JSONObject o = null; - try { - o = new JSONObject(itemJson); - Dictionary item = new Dictionary(); - item.put("type", o.optString("type")); - item.put("product_id", o.optString("productId")); - item.put("title", o.optString("title")); - item.put("description", o.optString("description")); - item.put("price", o.optString("price")); - item.put("price_currency_code", o.optString("price_currency_code")); - item.put("price_amount", 0.000001d * o.optLong("price_amount_micros")); - mSkuDetails.put(item.get("product_id").toString(), item); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - public void completeSkuDetail() { - GodotLib.calldeferred(purchaseCallbackId, "sku_details_complete", new Object[] { mSkuDetails }); - } - - public void errorSkuDetail(String errorMessage) { - GodotLib.calldeferred(purchaseCallbackId, "sku_details_error", new Object[] { errorMessage }); - } -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java new file mode 100644 index 0000000000..2047c88070 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java @@ -0,0 +1,50 @@ +/*************************************************************************/ +/* GodotRenderView.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot; + +import org.godotengine.godot.input.GodotInputHandler; + +import android.view.SurfaceView; + +public interface GodotRenderView { + abstract public SurfaceView getView(); + + abstract public void initInputDevices(); + + abstract public void queueOnRenderThread(Runnable event); + + abstract public void onActivityPaused(); + abstract public void onActivityResumed(); + + abstract public void onBackPressed(); + + abstract public GodotInputHandler getInputHandler(); +} 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..59bdbf7f8d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderer.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,18 +30,27 @@ package org.godotengine.godot; +import org.godotengine.godot.plugin.GodotPlugin; +import org.godotengine.godot.plugin.GodotPluginRegistry; +import org.godotengine.godot.utils.GLUtils; + +import android.content.Context; import android.opengl.GLSurfaceView; + import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; -import org.godotengine.godot.utils.GLUtils; /** * Godot's renderer implementation. */ 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 +58,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); + GodotLib.resize(null, width, height); + for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { + plugin.onGLSurfaceChanged(gl, width, height); } } public void onSurfaceCreated(GL10 gl, EGLConfig config) { - GodotLib.newcontext(GLUtils.use_32); + GodotLib.newcontext(null, 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/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java new file mode 100644 index 0000000000..2e59dbc0d0 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -0,0 +1,154 @@ +/*************************************************************************/ +/* GodotVulkanRenderView.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot; + +import org.godotengine.godot.input.GodotGestureHandler; +import org.godotengine.godot.input.GodotInputHandler; +import org.godotengine.godot.vulkan.VkRenderer; +import org.godotengine.godot.vulkan.VkSurfaceView; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.GestureDetector; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.SurfaceView; + +public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { + private final Godot godot; + private final GodotInputHandler mInputHandler; + private final GestureDetector mGestureDetector; + private final VkRenderer mRenderer; + + public GodotVulkanRenderView(Context context, Godot godot) { + super(context); + + this.godot = godot; + mInputHandler = new GodotInputHandler(this); + mGestureDetector = new GestureDetector(context, new GodotGestureHandler(this)); + mRenderer = new VkRenderer(); + + setFocusableInTouchMode(true); + startRenderer(mRenderer); + } + + @Override + public SurfaceView getView() { + return this; + } + + @Override + public void initInputDevices() { + mInputHandler.initInputDevices(); + } + + @Override + public void queueOnRenderThread(Runnable event) { + queueOnVkThread(event); + } + + @Override + public void onActivityPaused() { + onPause(); + } + + @Override + public void onActivityResumed() { + onResume(); + } + + @Override + public void onBackPressed() { + godot.onBackPressed(); + } + + @Override + public GodotInputHandler getInputHandler() { + return mInputHandler; + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + mGestureDetector.onTouchEvent(event); + return mInputHandler.onTouchEvent(event); + } + + @Override + public boolean onKeyUp(final int keyCode, KeyEvent event) { + return mInputHandler.onKeyUp(keyCode, event); + } + + @Override + public boolean onKeyDown(final int keyCode, KeyEvent event) { + return mInputHandler.onKeyDown(keyCode, event); + } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + return mInputHandler.onGenericMotionEvent(event); + } + + @Override + public boolean onCapturedPointerEvent(MotionEvent event) { + return mInputHandler.onGenericMotionEvent(event); + } + + @Override + public void onResume() { + super.onResume(); + + queueOnVkThread(new Runnable() { + @Override + public void run() { + // Resume the renderer + mRenderer.onVkResume(); + GodotLib.focusin(); + } + }); + } + + @Override + public void onPause() { + super.onPause(); + + queueOnVkThread(new Runnable() { + @Override + public void run() { + GodotLib.focusout(); + // Pause the renderer + mRenderer.onVkPause(); + } + }); + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java index 0d5521dd87..d1e8ae5ca9 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,16 +29,21 @@ /*************************************************************************/ package org.godotengine.godot.input; + +import org.godotengine.godot.*; + import android.content.Context; import android.os.Handler; import android.os.Message; +import android.text.InputFilter; +import android.text.InputType; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; + import java.lang.ref.WeakReference; -import org.godotengine.godot.*; public class GodotEditText extends EditText { // =========================================================== @@ -50,10 +55,12 @@ public class GodotEditText extends EditText { // =========================================================== // Fields // =========================================================== - private GodotView mView; + private GodotRenderView mRenderView; private GodotTextInputWrapper mInputWrapper; private EditHandler sHandler = new EditHandler(this); private String mOriginText; + private int mMaxInputLength = Integer.MAX_VALUE; + private boolean mMultiline = false; private static class EditHandler extends Handler { private final WeakReference<GodotEditText> mEdit; @@ -75,22 +82,26 @@ public class GodotEditText extends EditText { // =========================================================== public GodotEditText(final Context context) { super(context); - this.initView(); + initView(); } public GodotEditText(final Context context, final AttributeSet attrs) { super(context, attrs); - this.initView(); + initView(); } public GodotEditText(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); - this.initView(); + initView(); } protected void initView() { - this.setPadding(0, 0, 0, 0); - this.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); + setPadding(0, 0, 0, 0); + setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE); + } + + public boolean isMultiline() { + return mMultiline; } private void handleMessage(final Message msg) { @@ -100,11 +111,25 @@ public class GodotEditText extends EditText { String text = edit.mOriginText; if (edit.requestFocus()) { edit.removeTextChangedListener(edit.mInputWrapper); + setMaxInputLength(edit); edit.setText(""); edit.append(text); + if (msg.arg2 != -1) { + edit.setSelection(msg.arg1, msg.arg2); + edit.mInputWrapper.setSelection(true); + } else { + edit.mInputWrapper.setSelection(false); + } + + int inputType = InputType.TYPE_CLASS_TEXT; + if (edit.isMultiline()) { + inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE; + } + edit.setInputType(inputType); + edit.mInputWrapper.setOriginText(text); edit.addTextChangedListener(edit.mInputWrapper); - final InputMethodManager imm = (InputMethodManager)mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + final InputMethodManager imm = (InputMethodManager)mRenderView.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(edit, 0); } } break; @@ -113,22 +138,28 @@ public class GodotEditText extends EditText { GodotEditText edit = (GodotEditText)msg.obj; edit.removeTextChangedListener(mInputWrapper); - final InputMethodManager imm = (InputMethodManager)mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + final InputMethodManager imm = (InputMethodManager)mRenderView.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(edit.getWindowToken(), 0); - edit.mView.requestFocus(); + edit.mRenderView.getView().requestFocus(); } break; } } + private void setMaxInputLength(EditText p_edit_text) { + InputFilter[] filters = new InputFilter[1]; + filters[0] = new InputFilter.LengthFilter(this.mMaxInputLength); + p_edit_text.setFilters(filters); + } + // =========================================================== // Getter & Setter // =========================================================== - public void setView(final GodotView view) { - this.mView = view; + public void setView(final GodotRenderView view) { + mRenderView = view; if (mInputWrapper == null) - mInputWrapper = new GodotTextInputWrapper(mView, this); - this.setOnEditorActionListener(mInputWrapper); - view.requestFocus(); + mInputWrapper = new GodotTextInputWrapper(mRenderView, this); + setOnEditorActionListener(mInputWrapper); + view.getView().requestFocus(); } // =========================================================== @@ -136,25 +167,60 @@ public class GodotEditText extends EditText { // =========================================================== @Override public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { - super.onKeyDown(keyCode, keyEvent); - - /* Let GlSurfaceView get focus if back key is input. */ + /* Let SurfaceView get focus if back key is input. */ if (keyCode == KeyEvent.KEYCODE_BACK) { - this.mView.requestFocus(); + mRenderView.getView().requestFocus(); + } + + // pass event to godot in special cases + if (needHandlingInGodot(keyCode, keyEvent) && mRenderView.getInputHandler().onKeyDown(keyCode, keyEvent)) { + return true; + } else { + return super.onKeyDown(keyCode, keyEvent); + } + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent keyEvent) { + if (needHandlingInGodot(keyCode, keyEvent) && mRenderView.getInputHandler().onKeyUp(keyCode, keyEvent)) { + return true; + } else { + return super.onKeyUp(keyCode, keyEvent); } + } - return true; + private boolean needHandlingInGodot(int keyCode, KeyEvent keyEvent) { + boolean isArrowKey = keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN || + keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT; + boolean isModifiedKey = keyEvent.isAltPressed() || keyEvent.isCtrlPressed() || keyEvent.isSymPressed() || + keyEvent.isFunctionPressed() || keyEvent.isMetaPressed(); + return isArrowKey || keyCode == KeyEvent.KEYCODE_TAB || KeyEvent.isModifierKey(keyCode) || + isModifiedKey; } // =========================================================== // Methods // =========================================================== - public void showKeyboard(String p_existing_text) { - this.mOriginText = p_existing_text; + public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { + int maxInputLength = (p_max_input_length <= 0) ? Integer.MAX_VALUE : p_max_input_length; + if (p_cursor_start == -1) { // cursor position not given + this.mOriginText = p_existing_text; + this.mMaxInputLength = maxInputLength; + } else if (p_cursor_end == -1) { // not text selection + this.mOriginText = p_existing_text.substring(0, p_cursor_start); + this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_start); + } else { + this.mOriginText = p_existing_text.substring(0, p_cursor_end); + this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_end); + } + + this.mMultiline = p_multiline; final Message msg = new Message(); msg.what = HANDLER_OPEN_IME_KEYBOARD; msg.obj = this; + msg.arg1 = p_cursor_start; + msg.arg2 = p_cursor_end; sHandler.sendMessage(msg); } diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java index 1a38a9c3d2..2c39d06832 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,26 +30,25 @@ package org.godotengine.godot.input; -import android.util.Log; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.GodotRenderView; + import android.view.GestureDetector; import android.view.MotionEvent; -import org.godotengine.godot.GodotLib; -import org.godotengine.godot.GodotView; /** - * Handles gesture input related events for the {@link GodotView} view. + * Handles gesture input related events for the {@link GodotRenderView} view. * https://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener */ public class GodotGestureHandler extends GestureDetector.SimpleOnGestureListener { + private final GodotRenderView mRenderView; - private final GodotView godotView; - - public GodotGestureHandler(GodotView godotView) { - this.godotView = godotView; + public GodotGestureHandler(GodotRenderView godotView) { + mRenderView = godotView; } private void queueEvent(Runnable task) { - godotView.queueEvent(task); + mRenderView.queueOnRenderThread(task); } @Override @@ -75,10 +74,11 @@ public class GodotGestureHandler extends GestureDetector.SimpleOnGestureListener //Log.i("GodotGesture", "onDoubleTap"); final int x = Math.round(event.getX()); final int y = Math.round(event.getY()); + final int buttonMask = event.getButtonState(); queueEvent(new Runnable() { @Override public void run() { - GodotLib.doubletap(x, y); + GodotLib.doubleTap(buttonMask, x, y); } }); return true; diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java index b2b88088e8..435b8b325f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,37 +32,48 @@ package org.godotengine.godot.input; import static org.godotengine.godot.utils.GLUtils.DEBUG; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.GodotRenderView; +import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener; + +import android.os.Build; import android.util.Log; +import android.util.SparseArray; +import android.util.SparseIntArray; import android.view.InputDevice; import android.view.InputDevice.MotionRange; import android.view.KeyEvent; import android.view.MotionEvent; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import org.godotengine.godot.GodotLib; -import org.godotengine.godot.GodotView; -import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener; +import java.util.Map; +import java.util.Set; /** - * Handles input related events for the {@link GodotView} view. + * Handles input related events for the {@link GodotRenderView} view. */ public class GodotInputHandler implements InputDeviceListener { + private final GodotRenderView mRenderView; + private final InputManagerCompat mInputManager; - private final ArrayList<Joystick> joysticksDevices = new ArrayList<Joystick>(); + private final String tag = this.getClass().getSimpleName(); - private final GodotView godotView; - private final InputManagerCompat inputManager; + private final SparseIntArray mJoystickIds = new SparseIntArray(4); + private final SparseArray<Joystick> mJoysticksDevices = new SparseArray<Joystick>(4); - public GodotInputHandler(GodotView godotView) { - this.godotView = godotView; - this.inputManager = InputManagerCompat.Factory.getInputManager(godotView.getContext()); - this.inputManager.registerInputDeviceListener(this, null); + public GodotInputHandler(GodotRenderView godotView) { + mRenderView = godotView; + mInputManager = InputManagerCompat.Factory.getInputManager(mRenderView.getView().getContext()); + mInputManager.registerInputDeviceListener(this, null); } private void queueEvent(Runnable task) { - godotView.queueEvent(task); + mRenderView.queueOnRenderThread(task); } private boolean isKeyEvent_GameDevice(int source) { @@ -80,39 +91,40 @@ public class GodotInputHandler implements InputDeviceListener { if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { return false; - }; + } int source = event.getSource(); if (isKeyEvent_GameDevice(source)) { - - final int button = getGodotButton(keyCode); - final int device_id = findJoystickDevice(event.getDeviceId()); - // Check if the device exists - if (device_id > -1) { + final int deviceId = event.getDeviceId(); + if (mJoystickIds.indexOfKey(deviceId) >= 0) { + final int button = getGodotButton(keyCode); + final int godotJoyId = mJoystickIds.get(deviceId); + queueEvent(new Runnable() { @Override public void run() { - GodotLib.joybutton(device_id, button, false); + GodotLib.joybutton(godotJoyId, button, false); } }); } } else { + final int scanCode = event.getScanCode(); final int chr = event.getUnicodeChar(0); queueEvent(new Runnable() { @Override public void run() { - GodotLib.key(keyCode, chr, false); + GodotLib.key(keyCode, scanCode, chr, false); } }); - }; + } return true; } public boolean onKeyDown(final int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { - godotView.onBackPressed(); + mRenderView.onBackPressed(); // press 'back' button should not terminate program //normal handle 'back' event in game logic return true; @@ -120,77 +132,136 @@ public class GodotInputHandler implements InputDeviceListener { if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { return false; - }; + } int source = event.getSource(); //Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD))); + final int deviceId = event.getDeviceId(); + // Check if source is a game device and that the device is a registered gamepad if (isKeyEvent_GameDevice(source)) { - if (event.getRepeatCount() > 0) // ignore key echo return true; - final int button = getGodotButton(keyCode); - final int device_id = findJoystickDevice(event.getDeviceId()); + if (mJoystickIds.indexOfKey(deviceId) >= 0) { + final int button = getGodotButton(keyCode); + final int godotJoyId = mJoystickIds.get(deviceId); - // Check if the device exists - if (device_id > -1) { queueEvent(new Runnable() { @Override public void run() { - GodotLib.joybutton(device_id, button, true); + GodotLib.joybutton(godotJoyId, button, true); } }); } } else { + final int scanCode = event.getScanCode(); final int chr = event.getUnicodeChar(0); queueEvent(new Runnable() { @Override public void run() { - GodotLib.key(keyCode, chr, true); + GodotLib.key(keyCode, scanCode, chr, true); } }); - }; + } return true; } - public boolean onGenericMotionEvent(MotionEvent event) { - if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) { + public boolean onTouchEvent(final MotionEvent event) { + // Mouse drag (mouse pressed and move) doesn't fire onGenericMotionEvent so this is needed + if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { + if (event.getAction() != MotionEvent.ACTION_MOVE) { + // we return true because every time a mouse event is fired, the event is already handled + // in onGenericMotionEvent, so by touch event we can say that the event is also handled + return true; + } + return handleMouseEvent(event); + } - final int device_id = findJoystickDevice(event.getDeviceId()); + final int evcount = event.getPointerCount(); + if (evcount == 0) + return true; + if (mRenderView != null) { + final float[] arr = new float[event.getPointerCount() * 3]; // pointerId1, x1, y1, pointerId2, etc... + + for (int i = 0; i < event.getPointerCount(); i++) { + arr[i * 3 + 0] = event.getPointerId(i); + arr[i * 3 + 1] = event.getX(i); + arr[i * 3 + 2] = event.getY(i); + } + final int action = event.getActionMasked(); + final int pointer_idx = event.getPointerId(event.getActionIndex()); + + mRenderView.queueOnRenderThread(new Runnable() { + @Override + public void run() { + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_POINTER_DOWN: { + GodotLib.touch(event.getSource(), action, pointer_idx, evcount, arr); + } break; + } + } + }); + } + return true; + } + + public boolean onGenericMotionEvent(MotionEvent event) { + if (event.isFromSource(InputDevice.SOURCE_JOYSTICK) && event.getAction() == MotionEvent.ACTION_MOVE) { // Check if the device exists - if (device_id > -1) { - Joystick joy = joysticksDevices.get(device_id); - - for (int i = 0; i < joy.axes.size(); i++) { - InputDevice.MotionRange range = joy.axes.get(i); - final float value = (event.getAxisValue(range.getAxis()) - range.getMin()) / range.getRange() * 2.0f - 1.0f; - final int idx = i; - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyaxis(device_id, idx, value); - } - }); + final int deviceId = event.getDeviceId(); + if (mJoystickIds.indexOfKey(deviceId) >= 0) { + final int godotJoyId = mJoystickIds.get(deviceId); + Joystick joystick = mJoysticksDevices.get(deviceId); + + for (int i = 0; i < joystick.axes.size(); i++) { + final int axis = joystick.axes.get(i); + final float value = event.getAxisValue(axis); + /** + * As all axes are polled for each event, only fire an axis event if the value has actually changed. + * Prevents flooding Godot with repeated events. + */ + if (joystick.axesValues.indexOfKey(axis) < 0 || (float)joystick.axesValues.get(axis) != value) { + // save value to prevent repeats + joystick.axesValues.put(axis, value); + final int godotAxisIdx = i; + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyaxis(godotJoyId, godotAxisIdx, value); + //Log.i(tag, "GodotLib.joyaxis("+godotJoyId+", "+godotAxisIdx+", "+value+");"); + } + }); + } } - for (int i = 0; i < joy.hats.size(); i += 2) { - final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis())); - final int hatY = Math.round(event.getAxisValue(joy.hats.get(i + 1).getAxis())); - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyhat(device_id, hatX, hatY); - } - }); + if (joystick.hasAxisHat) { + final int hatX = Math.round(event.getAxisValue(MotionEvent.AXIS_HAT_X)); + final int hatY = Math.round(event.getAxisValue(MotionEvent.AXIS_HAT_Y)); + if (joystick.hatX != hatX || joystick.hatY != hatY) { + joystick.hatX = hatX; + joystick.hatY = hatY; + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyhat(godotJoyId, hatX, hatY); + //Log.i(tag, "GodotLib.joyhat("+godotJoyId+", "+hatX+", "+hatY+");"); + } + }); + } } return true; } - } else if ((event.getSource() & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) { - final int x = Math.round(event.getX()); - final int y = Math.round(event.getY()); + } else if (event.isFromSource(InputDevice.SOURCE_STYLUS)) { + final float x = event.getX(); + final float y = event.getY(); final int type = event.getAction(); queueEvent(new Runnable() { @Override @@ -199,6 +270,11 @@ public class GodotInputHandler implements InputDeviceListener { } }); return true; + + } else if (event.isFromSource(InputDevice.SOURCE_MOUSE) || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return handleMouseEvent(event); + } } return false; @@ -206,77 +282,108 @@ public class GodotInputHandler implements InputDeviceListener { public void initInputDevices() { /* initially add input devices*/ - int[] deviceIds = inputManager.getInputDeviceIds(); + int[] deviceIds = mInputManager.getInputDeviceIds(); for (int deviceId : deviceIds) { - InputDevice device = inputManager.getInputDevice(deviceId); + InputDevice device = mInputManager.getInputDevice(deviceId); if (DEBUG) { - Log.v("GodotView", String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName())); + Log.v("GodotInputHandler", String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName())); } onInputDeviceAdded(deviceId); } } + private int assignJoystickIdNumber(int deviceId) { + int godotJoyId = 0; + while (mJoystickIds.indexOfValue(godotJoyId) >= 0) { + godotJoyId++; + } + mJoystickIds.put(deviceId, godotJoyId); + return godotJoyId; + } + @Override public void onInputDeviceAdded(int deviceId) { - int id = findJoystickDevice(deviceId); - // Check if the device has not been already added - if (id < 0) { - InputDevice device = inputManager.getInputDevice(deviceId); - //device can be null if deviceId is not found - if (device != null) { - int sources = device.getSources(); - if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || - ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) { - id = joysticksDevices.size(); - - Joystick joy = new Joystick(); - joy.device_id = deviceId; - joy.name = device.getName(); - joy.axes = new ArrayList<InputDevice.MotionRange>(); - joy.hats = new ArrayList<InputDevice.MotionRange>(); - - List<InputDevice.MotionRange> ranges = device.getMotionRanges(); - Collections.sort(ranges, new RangeComparator()); - - for (InputDevice.MotionRange range : ranges) { - if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) { - joy.hats.add(range); - } else { - joy.axes.add(range); - } - } - joysticksDevices.add(joy); + if (mJoystickIds.indexOfKey(deviceId) >= 0) { + return; + } + + InputDevice device = mInputManager.getInputDevice(deviceId); + //device can be null if deviceId is not found + if (device == null) { + return; + } + + int sources = device.getSources(); + + // Device may not be a joystick or gamepad + if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD && + (sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) { + return; + } + + // Assign first available number. Re-use numbers where possible. + final int id = assignJoystickIdNumber(deviceId); - final int device_id = id; - final String name = joy.name; - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyconnectionchanged(device_id, true, name); - } - }); + final Joystick joystick = new Joystick(); + joystick.device_id = deviceId; + joystick.name = device.getName(); + + //Helps with creating new joypad mappings. + Log.i(tag, "=== New Input Device: " + joystick.name); + + Set<Integer> already = new HashSet<Integer>(); + for (InputDevice.MotionRange range : device.getMotionRanges()) { + boolean isJoystick = range.isFromSource(InputDevice.SOURCE_JOYSTICK); + boolean isGamepad = range.isFromSource(InputDevice.SOURCE_GAMEPAD); + //Log.i(tag, "axis: "+range.getAxis()+ ", isJoystick: "+isJoystick+", isGamepad: "+isGamepad); + if (!isJoystick && !isGamepad) { + continue; + } + final int axis = range.getAxis(); + if (axis == MotionEvent.AXIS_HAT_X || axis == MotionEvent.AXIS_HAT_Y) { + joystick.hasAxisHat = true; + } else { + if (!already.contains(axis)) { + already.add(axis); + joystick.axes.add(axis); + } else { + Log.w(tag, " - DUPLICATE AXIS VALUE IN LIST: " + axis); } } } + Collections.sort(joystick.axes); + for (int idx = 0; idx < joystick.axes.size(); idx++) { + //Helps with creating new joypad mappings. + Log.i(tag, " - Mapping Android axis " + joystick.axes.get(idx) + " to Godot axis " + idx); + } + mJoysticksDevices.put(deviceId, joystick); + + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyconnectionchanged(id, true, joystick.name); + } + }); } @Override public void onInputDeviceRemoved(int deviceId) { - final int device_id = findJoystickDevice(deviceId); - - // Check if the evice has not been already removed - if (device_id > -1) { - joysticksDevices.remove(device_id); - - queueEvent(new Runnable() { - @Override - public void run() { - GodotLib.joyconnectionchanged(device_id, false, ""); - } - }); + // Check if the device has not been already removed + if (mJoystickIds.indexOfKey(deviceId) < 0) { + return; } + final int godotJoyId = mJoystickIds.get(deviceId); + mJoystickIds.delete(deviceId); + mJoysticksDevices.delete(deviceId); + + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.joyconnectionchanged(godotJoyId, false, ""); + } + }); } @Override @@ -357,13 +464,57 @@ public class GodotInputHandler implements InputDeviceListener { return button; } - private int findJoystickDevice(int device_id) { - for (int i = 0; i < joysticksDevices.size(); i++) { - if (joysticksDevices.get(i).device_id == device_id) { - return i; + private boolean handleMouseEvent(final MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_HOVER_ENTER: + case MotionEvent.ACTION_HOVER_MOVE: + case MotionEvent.ACTION_HOVER_EXIT: { + final float x = event.getX(); + final float y = event.getY(); + final int type = event.getAction(); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.hover(type, x, y); + } + }); + return true; + } + case MotionEvent.ACTION_BUTTON_PRESS: + case MotionEvent.ACTION_BUTTON_RELEASE: + case MotionEvent.ACTION_MOVE: { + final float x = event.getX(); + final float y = event.getY(); + final int buttonsMask = event.getButtonState(); + final int action = event.getAction(); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.touch(event.getSource(), action, 0, 1, new float[] { 0, x, y }, buttonsMask); + } + }); + return true; + } + case MotionEvent.ACTION_SCROLL: { + final float x = event.getX(); + final float y = event.getY(); + final int buttonsMask = event.getButtonState(); + final int action = event.getAction(); + final float verticalFactor = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + final float horizontalFactor = event.getAxisValue(MotionEvent.AXIS_HSCROLL); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.touch(event.getSource(), action, 0, 1, new float[] { 0, x, y }, buttonsMask, verticalFactor, horizontalFactor); + } + }); + } + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_UP: { + // we can safely ignore these cases because they are always come beside ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE + return true; } } - - return -1; + return false; } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java index 3a154f1bf3..3e0e6a65fd 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,6 +29,9 @@ /*************************************************************************/ package org.godotengine.godot.input; + +import org.godotengine.godot.*; + import android.content.Context; import android.text.Editable; import android.text.TextWatcher; @@ -37,7 +40,6 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; -import org.godotengine.godot.*; public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListener { // =========================================================== @@ -48,17 +50,18 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene // =========================================================== // Fields // =========================================================== - private final GodotView mView; + private final GodotRenderView mRenderView; private final GodotEditText mEdit; private String mOriginText; + private boolean mHasSelection; // =========================================================== // Constructors // =========================================================== - public GodotTextInputWrapper(final GodotView view, final GodotEditText edit) { - this.mView = view; - this.mEdit = edit; + public GodotTextInputWrapper(final GodotRenderView view, final GodotEditText edit) { + mRenderView = view; + mEdit = edit; } // =========================================================== @@ -66,13 +69,17 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene // =========================================================== private boolean isFullScreenEdit() { - final TextView textField = this.mEdit; + final TextView textField = mEdit; final InputMethodManager imm = (InputMethodManager)textField.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); return imm.isFullscreenMode(); } public void setOriginText(final String originText) { - this.mOriginText = originText; + mOriginText = originText; + } + + public void setSelection(boolean selection) { + mHasSelection = selection; } // =========================================================== @@ -87,12 +94,17 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene public void beforeTextChanged(final CharSequence pCharSequence, final int start, final int count, final int after) { //Log.d(TAG, "beforeTextChanged(" + pCharSequence + ")start: " + start + ",count: " + count + ",after: " + after); - mView.queueEvent(new Runnable() { + mRenderView.queueOnRenderThread(new Runnable() { @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); + + if (mHasSelection) { + mHasSelection = false; + break; + } } } }); @@ -106,12 +118,17 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene for (int i = start; i < start + count; ++i) { newChars[i - start] = pCharSequence.charAt(i); } - mView.queueEvent(new Runnable() { + mRenderView.queueOnRenderThread(new Runnable() { @Override public void run() { for (int i = 0; i < count; ++i) { - GodotLib.key(0, newChars[i], true); - GodotLib.key(0, newChars[i], false); + int key = newChars[i]; + if ((key == '\n') && !mEdit.isMultiline()) { + // Return keys are handled through action events + continue; + } + GodotLib.key(0, 0, key, true); + GodotLib.key(0, 0, key, false); } } }); @@ -119,23 +136,28 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene @Override public boolean onEditorAction(final TextView pTextView, final int pActionID, final KeyEvent pKeyEvent) { - if (this.mEdit == pTextView && this.isFullScreenEdit()) { + if (mEdit == pTextView && isFullScreenEdit()) { final String characters = pKeyEvent.getCharacters(); - mView.queueEvent(new Runnable() { + mRenderView.queueOnRenderThread(new Runnable() { @Override public void run() { for (int i = 0; i < characters.length(); i++) { final int ch = characters.codePointAt(i); - GodotLib.key(0, ch, true); - GodotLib.key(0, ch, false); + GodotLib.key(0, 0, ch, true); + GodotLib.key(0, 0, ch, false); } } }); } if (pActionID == EditorInfo.IME_ACTION_DONE) { - this.mView.requestFocus(); + // Enter key has been pressed + GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, true); + GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, false); + + mRenderView.getView().requestFocus(); + return true; } return false; } diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java index 4042c42e9d..62810ad3a4 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java @@ -120,7 +120,6 @@ public interface InputManagerCompat { * Use this to construct a compatible InputManager. */ public static class Factory { - /** * Constructs and returns a compatible InputManger * diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java index e4bafa7ff9..61828dccae 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java @@ -23,12 +23,12 @@ import android.os.Build; import android.os.Handler; import android.view.InputDevice; import android.view.MotionEvent; + import java.util.HashMap; import java.util.Map; @TargetApi(Build.VERSION_CODES.JELLY_BEAN) public class InputManagerV16 implements InputManagerCompat { - private final InputManager mInputManager; private final Map<InputManagerCompat.InputDeviceListener, V16InputDeviceListener> mListeners; diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java b/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java index 0c1bdb32aa..4b7318c718 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,8 +30,10 @@ package org.godotengine.godot.input; -import android.view.InputDevice.MotionRange; +import android.util.SparseArray; + import java.util.ArrayList; +import java.util.List; /** * POJO class to represent a Joystick input device. @@ -39,6 +41,12 @@ import java.util.ArrayList; class Joystick { int device_id; String name; - ArrayList<MotionRange> axes; - ArrayList<MotionRange> hats; + List<Integer> axes = new ArrayList<Integer>(); + protected boolean hasAxisHat = false; + /* + * Keep track of values so we can prevent flooding the engine with useless events. + */ + protected final SparseArray axesValues = new SparseArray<Float>(4); + protected int hatX; + protected int hatY; } diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/ConsumeTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/ConsumeTask.java deleted file mode 100644 index 95cc48f536..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/ConsumeTask.java +++ /dev/null @@ -1,116 +0,0 @@ -/*************************************************************************/ -/* ConsumeTask.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.payments; - -import android.content.Context; -import android.os.AsyncTask; -import android.os.RemoteException; -import com.android.vending.billing.IInAppBillingService; -import java.lang.ref.WeakReference; - -abstract public class ConsumeTask { - - private Context context; - private IInAppBillingService mService; - - private String mSku; - private String mToken; - - private static class ConsumeAsyncTask extends AsyncTask<String, String, String> { - - private WeakReference<ConsumeTask> mTask; - - ConsumeAsyncTask(ConsumeTask consume) { - mTask = new WeakReference<>(consume); - } - - @Override - protected String doInBackground(String... strings) { - ConsumeTask consume = mTask.get(); - if (consume != null) { - return consume.doInBackground(strings); - } - return null; - } - - @Override - protected void onPostExecute(String param) { - ConsumeTask consume = mTask.get(); - if (consume != null) { - consume.onPostExecute(param); - } - } - } - - public ConsumeTask(IInAppBillingService mService, Context context) { - this.context = context; - this.mService = mService; - } - - public void consume(final String sku) { - mSku = sku; - PaymentsCache pc = new PaymentsCache(context); - Boolean isBlocked = pc.getConsumableFlag("block", sku); - mToken = pc.getConsumableValue("token", sku); - if (!isBlocked && mToken == null) { - // Consuming task is processing - } else if (!isBlocked) { - return; - } else if (mToken == null) { - this.error("No token for sku:" + sku); - return; - } - new ConsumeAsyncTask(this).execute(); - } - - private String doInBackground(String... params) { - try { - int response = mService.consumePurchase(3, context.getPackageName(), mToken); - if (response == 0 || response == 8) { - return null; - } - } catch (RemoteException e) { - return e.getMessage(); - } - return "Some error"; - } - - private void onPostExecute(String param) { - if (param == null) { - success(new PaymentsCache(context).getConsumableValue("ticket", mSku)); - } else { - error(param); - } - } - - abstract protected void success(String ticket); - abstract protected void error(String message); -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/HandlePurchaseTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/HandlePurchaseTask.java deleted file mode 100644 index 23d693cc8c..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/HandlePurchaseTask.java +++ /dev/null @@ -1,93 +0,0 @@ -/*************************************************************************/ -/* HandlePurchaseTask.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.payments; - -import android.app.Activity; -import android.content.Intent; -import org.json.JSONException; -import org.json.JSONObject; - -abstract public class HandlePurchaseTask { - - private Activity context; - - public HandlePurchaseTask(Activity context) { - this.context = context; - } - - public void handlePurchaseRequest(int resultCode, Intent data) { - //Log.d("XXX", "Handling purchase response"); - if (resultCode == Activity.RESULT_OK) { - try { - //int responseCode = data.getIntExtra("RESPONSE_CODE", 0); - PaymentsCache pc = new PaymentsCache(context); - - String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); - //Log.d("XXX", "Purchase data:" + purchaseData); - String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE"); - //Log.d("XXX", "Purchase signature:" + dataSignature); - //Log.d("SARLANGA", purchaseData); - - JSONObject jo = new JSONObject(purchaseData); - //String sku = jo.getString("productId"); - //alert("You have bought the " + sku + ". Excellent choice, aventurer!"); - //String orderId = jo.getString("orderId"); - //String packageName = jo.getString("packageName"); - String productId = jo.getString("productId"); - //Long purchaseTime = jo.getLong("purchaseTime"); - //Integer state = jo.getInt("purchaseState"); - String developerPayload = jo.getString("developerPayload"); - String purchaseToken = jo.getString("purchaseToken"); - - if (!pc.getConsumableValue("validation_hash", productId).equals(developerPayload)) { - error("Untrusted callback"); - return; - } - //Log.d("XXX", "Este es el product ID:" + productId); - pc.setConsumableValue("ticket_signautre", productId, dataSignature); - pc.setConsumableValue("ticket", productId, purchaseData); - pc.setConsumableFlag("block", productId, true); - pc.setConsumableValue("token", productId, purchaseToken); - - success(productId, dataSignature, purchaseData); - return; - } catch (JSONException e) { - error(e.getMessage()); - } - } else if (resultCode == Activity.RESULT_CANCELED) { - canceled(); - } - } - - abstract protected void success(String sku, String signature, String ticket); - abstract protected void error(String message); - abstract protected void canceled(); -} 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 deleted file mode 100644 index 90b958266b..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java +++ /dev/null @@ -1,419 +0,0 @@ -/*************************************************************************/ -/* PaymentsManager.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.payments; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.text.TextUtils; -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; - -public class PaymentsManager { - - public static final int BILLING_RESPONSE_RESULT_OK = 0; - public static final int REQUEST_CODE_FOR_PURCHASE = 0x1001; - private static boolean auto_consume = true; - - private Activity activity; - IInAppBillingService mService; - - public void setActivity(Activity activity) { - this.activity = activity; - } - - public static PaymentsManager createManager(Activity activity) { - PaymentsManager manager = new PaymentsManager(activity); - return manager; - } - - private PaymentsManager(Activity activity) { - this.activity = activity; - } - - public PaymentsManager initService() { - Intent intent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); - intent.setPackage("com.android.vending"); - activity.bindService( - intent, - mServiceConn, - Context.BIND_AUTO_CREATE); - return this; - } - - public void destroy() { - if (mService != null) { - activity.unbindService(mServiceConn); - } - } - - ServiceConnection mServiceConn = new ServiceConnection() { - @Override - public void onServiceDisconnected(ComponentName name) { - mService = null; - - // At this stage, godotPaymentV3 might not have been initialized yet. - if (godotPaymentV3 != null) { - godotPaymentV3.callbackDisconnected(); - } - } - - @Override - 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(); - } - } - }; - - public void requestPurchase(final String sku, String transactionId) { - new PurchaseTask(mService, activity) { - @Override - protected void error(String message) { - godotPaymentV3.callbackFail(message); - } - - @Override - protected void canceled() { - godotPaymentV3.callbackCancel(); - } - - @Override - protected void alreadyOwned() { - godotPaymentV3.callbackAlreadyOwned(sku); - } - } - .purchase(sku, transactionId); - } - - public boolean isConnected() { - return mService != null; - } - - public void consumeUnconsumedPurchases() { - new ReleaseAllConsumablesTask(mService, activity) { - @Override - protected void success(String sku, String receipt, String signature, String token) { - godotPaymentV3.callbackSuccessProductMassConsumed(receipt, signature, sku); - } - - @Override - protected void error(String message) { - Log.d("godot", "consumeUnconsumedPurchases :" + message); - godotPaymentV3.callbackFailConsume(message); - } - - @Override - protected void notRequired() { - Log.d("godot", "callbackSuccessNoUnconsumedPurchases :"); - godotPaymentV3.callbackSuccessNoUnconsumedPurchases(); - } - } - .consumeItAll(); - } - - public void requestPurchased() { - try { - PaymentsCache pc = new PaymentsCache(activity); - - String continueToken = null; - - do { - Bundle bundle = mService.getPurchases(3, activity.getPackageName(), "inapp", continueToken); - - if (bundle.getInt("RESPONSE_CODE") == 0) { - - final ArrayList<String> myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); - final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST"); - - if (myPurchases == null || myPurchases.size() == 0) { - godotPaymentV3.callbackPurchased("", "", ""); - return; - } - - for (int i = 0; i < myPurchases.size(); i++) { - - try { - String receipt = myPurchases.get(i); - JSONObject inappPurchaseData = new JSONObject(receipt); - String sku = inappPurchaseData.getString("productId"); - String token = inappPurchaseData.getString("purchaseToken"); - String signature = mySignatures.get(i); - - pc.setConsumableValue("ticket_signautre", sku, signature); - pc.setConsumableValue("ticket", sku, receipt); - pc.setConsumableFlag("block", sku, true); - pc.setConsumableValue("token", sku, token); - - godotPaymentV3.callbackPurchased(receipt, signature, sku); - } catch (JSONException e) { - } - } - } - continueToken = bundle.getString("INAPP_CONTINUATION_TOKEN"); - Log.d("godot", "continue token = " + continueToken); - } while (!TextUtils.isEmpty(continueToken)); - } catch (Exception e) { - Log.d("godot", "Error requesting purchased products:" + e.getClass().getName() + ":" + e.getMessage()); - } - } - - public void processPurchaseResponse(int resultCode, Intent data) { - new HandlePurchaseTask(activity) { - @Override - protected void success(final String sku, final String signature, final String ticket) { - godotPaymentV3.callbackSuccess(ticket, signature, sku); - - if (auto_consume) { - new ConsumeTask(mService, activity) { - @Override - protected void success(String ticket) { - } - - @Override - protected void error(String message) { - godotPaymentV3.callbackFail(message); - } - } - .consume(sku); - } - } - - @Override - protected void error(String message) { - godotPaymentV3.callbackFail(message); - } - - @Override - protected void canceled() { - godotPaymentV3.callbackCancel(); - } - } - .handlePurchaseRequest(resultCode, data); - } - - public void validatePurchase(String purchaseToken, final String sku) { - - new ValidateTask(activity, godotPaymentV3) { - @Override - protected void success() { - - new ConsumeTask(mService, activity) { - @Override - protected void success(String ticket) { - godotPaymentV3.callbackSuccess(ticket, null, sku); - } - - @Override - protected void error(String message) { - godotPaymentV3.callbackFail(message); - } - } - .consume(sku); - } - - @Override - protected void error(String message) { - godotPaymentV3.callbackFail(message); - } - - @Override - protected void canceled() { - godotPaymentV3.callbackCancel(); - } - } - .validatePurchase(sku); - } - - public void setAutoConsume(boolean autoConsume) { - auto_consume = autoConsume; - } - - public void consume(final String sku) { - new ConsumeTask(mService, activity) { - @Override - protected void success(String ticket) { - godotPaymentV3.callbackSuccessProductMassConsumed(ticket, "", sku); - } - - @Override - protected void error(String message) { - godotPaymentV3.callbackFailConsume(message); - } - } - .consume(sku); - } - - // Workaround to bug where sometimes response codes come as Long instead of Integer - int getResponseCodeFromBundle(Bundle b) { - Object o = b.get("RESPONSE_CODE"); - if (o == null) { - //logDebug("Bundle with null response code, assuming OK (known issue)"); - return BILLING_RESPONSE_RESULT_OK; - } else if (o instanceof Integer) - return ((Integer)o).intValue(); - else if (o instanceof Long) - return (int)((Long)o).longValue(); - else { - //logError("Unexpected type for bundle response code."); - //logError(o.getClass().getName()); - throw new RuntimeException("Unexpected type for bundle response code: " + o.getClass().getName()); - } - } - - /** - * Returns a human-readable description for the given response code. - * - * @param code The response code - * @return A human-readable string explaining the result code. - * It also includes the result code numerically. - */ - public static String getResponseDesc(int code) { - String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/" - + - "3:Billing Unavailable/4:Item unavailable/" - + - "5:Developer Error/6:Error/7:Item Already Owned/" - + - "8:Item not owned") - .split("/"); - String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/" - + - "-1002:Bad response received/" - + - "-1003:Purchase signature verification failed/" - + - "-1004:Send intent failed/" - + - "-1005:User cancelled/" - + - "-1006:Unknown purchase response/" - + - "-1007:Missing token/" - + - "-1008:Unknown error/" - + - "-1009:Subscriptions not available/" - + - "-1010:Invalid consumption attempt") - .split("/"); - - if (code <= -1000) { - int index = -1000 - code; - if (index >= 0 && index < iabhelper_msgs.length) - return iabhelper_msgs[index]; - else - return String.valueOf(code) + ":Unknown IAB Helper Error"; - } else if (code < 0 || code >= iab_msgs.length) - return String.valueOf(code) + ":Unknown"; - else - return iab_msgs[code]; - } - - public void querySkuDetails(final String[] list) { - (new Thread(new Runnable() { - @Override - public void run() { - ArrayList<String> skuList = new ArrayList<String>(Arrays.asList(list)); - if (skuList.size() == 0) { - return; - } - // Split the sku list in blocks of no more than 20 elements. - ArrayList<ArrayList<String>> packs = new ArrayList<ArrayList<String>>(); - ArrayList<String> tempList; - int n = skuList.size() / 20; - int mod = skuList.size() % 20; - for (int i = 0; i < n; i++) { - tempList = new ArrayList<String>(); - for (String s : skuList.subList(i * 20, i * 20 + 20)) { - tempList.add(s); - } - packs.add(tempList); - } - if (mod != 0) { - tempList = new ArrayList<String>(); - for (String s : skuList.subList(n * 20, n * 20 + mod)) { - tempList.add(s); - } - packs.add(tempList); - } - for (ArrayList<String> skuPartList : packs) { - Bundle querySkus = new Bundle(); - querySkus.putStringArrayList("ITEM_ID_LIST", skuPartList); - Bundle skuDetails = null; - try { - skuDetails = mService.getSkuDetails(3, activity.getPackageName(), "inapp", querySkus); - if (!skuDetails.containsKey("DETAILS_LIST")) { - int response = getResponseCodeFromBundle(skuDetails); - if (response != BILLING_RESPONSE_RESULT_OK) { - godotPaymentV3.errorSkuDetail(getResponseDesc(response)); - } else { - godotPaymentV3.errorSkuDetail("No error but no detail list."); - } - return; - } - - ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST"); - - for (String thisResponse : responseList) { - Log.d("godot", "response = " + thisResponse); - godotPaymentV3.addSkuDetail(thisResponse); - } - } catch (RemoteException e) { - e.printStackTrace(); - godotPaymentV3.errorSkuDetail("RemoteException error!"); - } - } - godotPaymentV3.completeSkuDetail(); - } - })) - .start(); - } - - private GodotPaymentV3 godotPaymentV3; - - public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) { - this.godotPaymentV3 = godotPaymentV3; - } -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/PurchaseTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/PurchaseTask.java deleted file mode 100644 index 09c9349124..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/PurchaseTask.java +++ /dev/null @@ -1,118 +0,0 @@ -/*************************************************************************/ -/* PurchaseTask.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.payments; - -import android.app.Activity; -import android.app.PendingIntent; -import android.content.Intent; -import android.content.IntentSender.SendIntentException; -import android.os.Bundle; -import android.os.RemoteException; -import android.util.Log; -import com.android.vending.billing.IInAppBillingService; - -abstract public class PurchaseTask { - - private Activity context; - - private IInAppBillingService mService; - public PurchaseTask(IInAppBillingService mService, Activity context) { - this.context = context; - this.mService = mService; - } - - private boolean isLooping = false; - - public void purchase(final String sku, final String transactionId) { - Log.d("XXX", "Starting purchase for: " + sku); - PaymentsCache pc = new PaymentsCache(context); - Boolean isBlocked = pc.getConsumableFlag("block", sku); - /* - if(isBlocked){ - Log.d("XXX", "Is awaiting payment confirmation"); - error("Awaiting payment confirmation"); - return; - } - */ - final String hash = transactionId; - - Bundle buyIntentBundle; - try { - buyIntentBundle = mService.getBuyIntent(3, context.getApplicationContext().getPackageName(), sku, "inapp", hash); - } catch (RemoteException e) { - //Log.d("XXX", "Error: " + e.getMessage()); - error(e.getMessage()); - return; - } - Object rc = buyIntentBundle.get("RESPONSE_CODE"); - int responseCode = 0; - if (rc == null) { - responseCode = PaymentsManager.BILLING_RESPONSE_RESULT_OK; - } else if (rc instanceof Integer) { - responseCode = ((Integer)rc).intValue(); - } else if (rc instanceof Long) { - responseCode = (int)((Long)rc).longValue(); - } - //Log.d("XXX", "Buy intent response code: " + responseCode); - if (responseCode == 1 || responseCode == 3 || responseCode == 4) { - canceled(); - return; - } - if (responseCode == 7) { - alreadyOwned(); - return; - } - - PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT"); - pc.setConsumableValue("validation_hash", sku, hash); - try { - if (context == null) { - //Log.d("XXX", "No context!"); - } - if (pendingIntent == null) { - //Log.d("XXX", "No pending intent"); - } - //Log.d("XXX", "Starting activity for purchase!"); - context.startIntentSenderForResult( - pendingIntent.getIntentSender(), - PaymentsManager.REQUEST_CODE_FOR_PURCHASE, - new Intent(), - Integer.valueOf(0), Integer.valueOf(0), - Integer.valueOf(0)); - } catch (SendIntentException e) { - error(e.getMessage()); - } - } - - abstract protected void error(String message); - abstract protected void canceled(); - abstract protected void alreadyOwned(); -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java b/platform/android/java/lib/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java deleted file mode 100644 index a101780511..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java +++ /dev/null @@ -1,141 +0,0 @@ -/*************************************************************************/ -/* ReleaseAllConsumablesTask.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.payments; - -import android.content.Context; -import android.os.AsyncTask; -import android.os.Bundle; -import android.util.Log; -import com.android.vending.billing.IInAppBillingService; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import org.json.JSONException; -import org.json.JSONObject; - -abstract public class ReleaseAllConsumablesTask { - - private Context context; - private IInAppBillingService mService; - - private static class ReleaseAllConsumablesAsyncTask extends AsyncTask<String, String, String> { - - private WeakReference<ReleaseAllConsumablesTask> mTask; - private String mSku; - private String mReceipt; - private String mSignature; - private String mToken; - - ReleaseAllConsumablesAsyncTask(ReleaseAllConsumablesTask task, String sku, String receipt, String signature, String token) { - mTask = new WeakReference<ReleaseAllConsumablesTask>(task); - - mSku = sku; - mReceipt = receipt; - mSignature = signature; - mToken = token; - } - - @Override - protected String doInBackground(String... params) { - ReleaseAllConsumablesTask consume = mTask.get(); - if (consume != null) { - return consume.doInBackground(mToken); - } - return null; - } - - @Override - protected void onPostExecute(String param) { - ReleaseAllConsumablesTask consume = mTask.get(); - if (consume != null) { - consume.success(mSku, mReceipt, mSignature, mToken); - } - } - } - - public ReleaseAllConsumablesTask(IInAppBillingService mService, Context context) { - this.context = context; - this.mService = mService; - } - - public void consumeItAll() { - try { - //Log.d("godot", "consumeItall for " + context.getPackageName()); - Bundle bundle = mService.getPurchases(3, context.getPackageName(), "inapp", null); - - if (bundle.getInt("RESPONSE_CODE") == 0) { - - final ArrayList<String> myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); - final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST"); - - if (myPurchases == null || myPurchases.size() == 0) { - //Log.d("godot", "No purchases!"); - notRequired(); - return; - } - - //Log.d("godot", "# products to be consumed:" + myPurchases.size()); - for (int i = 0; i < myPurchases.size(); i++) { - - try { - String receipt = myPurchases.get(i); - JSONObject inappPurchaseData = new JSONObject(receipt); - String sku = inappPurchaseData.getString("productId"); - String token = inappPurchaseData.getString("purchaseToken"); - String signature = mySignatures.get(i); - //Log.d("godot", "A punto de consumir un item con token:" + token + "\n" + receipt); - new ReleaseAllConsumablesAsyncTask(this, sku, receipt, signature, token).execute(); - } catch (JSONException e) { - } - } - } - } catch (Exception e) { - Log.d("godot", "Error releasing products:" + e.getClass().getName() + ":" + e.getMessage()); - } - } - - private String doInBackground(String token) { - try { - //Log.d("godot", "Requesting to consume an item with token ." + token); - int response = mService.consumePurchase(3, context.getPackageName(), token); - //Log.d("godot", "consumePurchase response: " + response); - if (response == 0 || response == 8) { - return null; - } - } catch (Exception e) { - Log.d("godot", "Error " + e.getClass().getName() + ":" + e.getMessage()); - } - return null; - } - - abstract protected void success(String sku, String receipt, String signature, String token); - abstract protected void error(String message); - abstract protected void notRequired(); -} 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 deleted file mode 100644 index dbb6b8a783..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java +++ /dev/null @@ -1,142 +0,0 @@ -/*************************************************************************/ -/* ValidateTask.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.payments; - -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; -import org.json.JSONObject; - -abstract public class ValidateTask { - - private Activity context; - private GodotPaymentV3 godotPaymentsV3; - private ProgressDialog dialog; - private String mSku; - - private static class ValidateAsyncTask extends AsyncTask<String, String, String> { - private WeakReference<ValidateTask> mTask; - - ValidateAsyncTask(ValidateTask task) { - mTask = new WeakReference<>(task); - } - - @Override - protected void onPreExecute() { - ValidateTask task = mTask.get(); - if (task != null) { - task.onPreExecute(); - } - } - - @Override - protected String doInBackground(String... params) { - ValidateTask task = mTask.get(); - if (task != null) { - return task.doInBackground(params); - } - return null; - } - - @Override - protected void onPostExecute(String response) { - ValidateTask task = mTask.get(); - if (task != null) { - task.onPostExecute(response); - } - } - } - - public ValidateTask(Activity context, GodotPaymentV3 godotPaymentsV3) { - this.context = context; - this.godotPaymentsV3 = godotPaymentsV3; - } - - public void validatePurchase(final String sku) { - mSku = sku; - new ValidateAsyncTask(this).execute(); - } - - private void onPreExecute() { - dialog = ProgressDialog.show(context, null, "Please wait..."); - } - - private String doInBackground(String... params) { - PaymentsCache pc = new PaymentsCache(context); - String url = godotPaymentsV3.getPurchaseValidationUrlPrefix(); - RequestParams param = new RequestParams(); - param.setUrl(url); - param.put("ticket", pc.getConsumableValue("ticket", mSku)); - param.put("purchaseToken", pc.getConsumableValue("token", mSku)); - param.put("sku", mSku); - //Log.d("XXX", "Haciendo request a " + url); - //Log.d("XXX", "ticket: " + pc.getConsumableValue("ticket", sku)); - //Log.d("XXX", "purchaseToken: " + pc.getConsumableValue("token", sku)); - //Log.d("XXX", "sku: " + sku); - param.put("package", context.getApplicationContext().getPackageName()); - HttpRequester requester = new HttpRequester(); - String jsonResponse = requester.post(param); - //Log.d("XXX", "Validation response:\n"+jsonResponse); - return jsonResponse; - } - - private void onPostExecute(String response) { - if (dialog != null) { - dialog.dismiss(); - dialog = null; - } - JSONObject j; - try { - j = new JSONObject(response); - if (j.getString("status").equals("OK")) { - success(); - return; - } else if (j.getString("status") != null) { - error(j.getString("message")); - } else { - error("Connection error"); - } - } catch (JSONException e) { - error(e.getMessage()); - } catch (Exception e) { - error(e.getMessage()); - } - } - - abstract protected void success(); - abstract protected void error(String message); - abstract protected void canceled(); -} 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..993f0e9127 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java @@ -0,0 +1,425 @@ +/*************************************************************************/ +/* GodotPlugin.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.plugin; + +import org.godotengine.godot.BuildConfig; +import org.godotengine.godot.Godot; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.Surface; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +/** + * 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 static final String TAG = GodotPlugin.class.getSimpleName(); + + private final Godot godot; + private final ConcurrentHashMap<String, SignalInfo> registeredSignals = new ConcurrentHashMap<>(); + + public GodotPlugin(Godot godot) { + this.godot = godot; + } + + /** + * Provides access to the Godot engine. + */ + protected Godot getGodot() { + return godot; + } + + /** + * Provides access to the underlying {@link Activity}. + */ + @Nullable + protected Activity getActivity() { + return godot.getActivity(); + } + + /** + * Register the plugin with Godot native code. + * + * This method is invoked on the render thread. + */ + public final void onRegisterPluginWithGodotNative() { + registeredSignals.putAll(registerPluginWithGodotNative(this, new GodotPluginInfoProvider() { + @NonNull + @Override + public String getPluginName() { + return GodotPlugin.this.getPluginName(); + } + + @NonNull + @Override + public List<String> getPluginMethods() { + return GodotPlugin.this.getPluginMethods(); + } + + @NonNull + @Override + public Set<SignalInfo> getPluginSignals() { + return GodotPlugin.this.getPluginSignals(); + } + + @NonNull + @Override + public Set<String> getPluginGDNativeLibrariesPaths() { + return GodotPlugin.this.getPluginGDNativeLibrariesPaths(); + } + })); + } + + /** + * Register the plugin with Godot native code. + * + * This method must be invoked on the render thread. + */ + public static Map<String, SignalInfo> registerPluginWithGodotNative(Object pluginObject, GodotPluginInfoProvider pluginInfoProvider) { + nativeRegisterSingleton(pluginInfoProvider.getPluginName(), pluginObject); + + Class clazz = pluginObject.getClass(); + Method[] methods = clazz.getDeclaredMethods(); + for (Method method : methods) { + boolean found = false; + + for (String s : pluginInfoProvider.getPluginMethods()) { + if (s.equals(method.getName())) { + found = true; + break; + } + } + if (!found) + continue; + + List<String> ptr = new ArrayList<>(); + + Class[] paramTypes = method.getParameterTypes(); + for (Class c : paramTypes) { + ptr.add(c.getName()); + } + + String[] pt = new String[ptr.size()]; + ptr.toArray(pt); + + nativeRegisterMethod(pluginInfoProvider.getPluginName(), method.getName(), method.getReturnType().getName(), pt); + } + + // Register the signals for this plugin. + Map<String, SignalInfo> registeredSignals = new HashMap<>(); + for (SignalInfo signalInfo : pluginInfoProvider.getPluginSignals()) { + String signalName = signalInfo.getName(); + nativeRegisterSignal(pluginInfoProvider.getPluginName(), signalName, signalInfo.getParamTypesNames()); + registeredSignals.put(signalName, signalInfo); + } + + // Get the list of gdnative libraries to register. + Set<String> gdnativeLibrariesPaths = pluginInfoProvider.getPluginGDNativeLibrariesPaths(); + if (!gdnativeLibrariesPaths.isEmpty()) { + nativeRegisterGDNativeLibraries(gdnativeLibrariesPaths.toArray(new String[0])); + } + + return registeredSignals; + } + + /** + * Invoked once during the Godot Android initialization process after creation of the + * {@link org.godotengine.godot.GodotRenderView} view. + * <p> + * The plugin can return a non-null {@link View} layout in order to add it to the Godot view + * hierarchy. + * + * @see Activity#onCreate(Bundle) + * @return the plugin's view to be included; null if no views should be included. + */ + @Nullable + public View onMainCreate(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 render thread when the Godot main loop has started. + */ + public void onGodotMainLoopStarted() {} + + /** + * 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) {} + + /** + * Invoked once per frame on the Vulkan thread after the frame is drawn. + */ + public void onVkDrawFrame() {} + + /** + * Called on the Vulkan thread after the surface is created and whenever the surface size + * changes. + */ + public void onVkSurfaceChanged(Surface surface, int width, int height) {} + + /** + * Called on the Vulkan thread when the surface is created or recreated. + */ + public void onVkSurfaceCreated(Surface surface) {} + + /** + * 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 List<String> getPluginMethods() { + return Collections.emptyList(); + } + + /** + * Returns the list of signals to be exposed to Godot. + */ + @NonNull + public Set<SignalInfo> getPluginSignals() { + return Collections.emptySet(); + } + + /** + * 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 render thread. + * + * @param action the action to run on the render thread + */ + protected void runOnRenderThread(Runnable action) { + godot.runOnRenderThread(action); + } + + /** + * Emit a registered Godot signal. + * @param signalName Name of the signal to emit. It will be validated against the set of registered signals. + * @param signalArgs Arguments used to populate the emitted signal. The arguments will be validated against the {@link SignalInfo} matching the registered signalName parameter. + */ + protected void emitSignal(final String signalName, final Object... signalArgs) { + try { + // Check that the given signal is among the registered set. + SignalInfo signalInfo = registeredSignals.get(signalName); + if (signalInfo == null) { + throw new IllegalArgumentException( + "Signal " + signalName + " is not registered for this plugin."); + } + emitSignal(getGodot(), getPluginName(), signalInfo, signalArgs); + } catch (IllegalArgumentException exception) { + Log.w(TAG, exception.getMessage()); + if (BuildConfig.DEBUG) { + throw exception; + } + } + } + + /** + * Emit a Godot signal. + * @param godot + * @param pluginName Name of the Godot plugin the signal will be emitted from. The plugin must already be registered with the Godot engine. + * @param signalInfo Information about the signal to emit. + * @param signalArgs Arguments used to populate the emitted signal. The arguments will be validated against the given {@link SignalInfo} parameter. + */ + public static void emitSignal(Godot godot, String pluginName, SignalInfo signalInfo, final Object... signalArgs) { + try { + if (signalInfo == null) { + throw new IllegalArgumentException("Signal must be non null."); + } + + // Validate the arguments count. + Class<?>[] signalParamTypes = signalInfo.getParamTypes(); + if (signalArgs.length != signalParamTypes.length) { + throw new IllegalArgumentException( + "Invalid arguments count. Should be " + signalParamTypes.length + " but is " + signalArgs.length); + } + + // Validate the argument's types. + for (int i = 0; i < signalParamTypes.length; i++) { + if (!signalParamTypes[i].isInstance(signalArgs[i])) { + throw new IllegalArgumentException( + "Invalid type for argument #" + i + ". Should be of type " + signalParamTypes[i].getName()); + } + } + + godot.runOnRenderThread(() -> nativeEmitSignal(pluginName, signalInfo.getName(), signalArgs)); + + } catch (IllegalArgumentException exception) { + Log.w(TAG, exception.getMessage()); + if (BuildConfig.DEBUG) { + throw exception; + } + } + } + + /** + * Used to setup a {@link GodotPlugin} instance. + * @param p_name Name of the instance. + */ + private static native void nativeRegisterSingleton(String p_name, Object object); + + /** + * 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 static native void nativeRegisterMethod(String p_sname, String p_name, String p_ret, String[] p_params); + + /** + * Used to register gdnative libraries bundled by the plugin. + * @param gdnlibPaths Paths to the libraries relative to the 'assets' directory. + */ + private static native void nativeRegisterGDNativeLibraries(String[] gdnlibPaths); + + /** + * Used to complete registration of the {@link GodotPlugin} instance's methods. + * @param pluginName Name of the plugin + * @param signalName Name of the signal to register + * @param signalParamTypes Signal parameters types + */ + private static native void nativeRegisterSignal(String pluginName, String signalName, String[] signalParamTypes); + + /** + * Used to emit signal by {@link GodotPlugin} instance. + * @param pluginName Name of the plugin + * @param signalName Name of the signal to emit + * @param signalParams Signal parameters + */ + private static native void nativeEmitSignal(String pluginName, String signalName, Object[] signalParams); +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/RequestParams.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java index 25fa10647d..c3084b036e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/RequestParams.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginInfoProvider.java @@ -1,12 +1,12 @@ /*************************************************************************/ -/* RequestParams.java */ +/* GodotPluginInfoProvider.java */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,58 +28,40 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.utils; +package org.godotengine.godot.plugin; + +import androidx.annotation.NonNull; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; import java.util.List; -import org.apache.http.NameValuePair; -import org.apache.http.message.BasicNameValuePair; +import java.util.Set; /** - * - * @author Luis Linietsky <luis.linietsky@gmail.com> + * Provides the set of information expected from a Godot plugin. */ -public class RequestParams { - - private HashMap<String, String> params; - private String url; - - public RequestParams() { - params = new HashMap<String, String>(); - } - - public void put(String key, String value) { - params.put(key, value); - } - - public String get(String key) { - return params.get(key); - } - - public void remove(Object key) { - params.remove(key); - } - - public boolean has(String key) { - return params.containsKey(key); - } - - public List<NameValuePair> toPairsList() { - List<NameValuePair> fields = new ArrayList<NameValuePair>(); +public interface GodotPluginInfoProvider { + /** + * Returns the name of the plugin. + */ + @NonNull + String getPluginName(); - for (String key : params.keySet()) { - fields.add(new BasicNameValuePair(key, this.get(key))); - } - return fields; - } + /** + * Returns the list of methods to be exposed to Godot. + */ + @NonNull + List<String> getPluginMethods(); - public String getUrl() { - return url; - } + /** + * Returns the list of signals to be exposed to Godot. + */ + @NonNull + Set<SignalInfo> getPluginSignals(); - public void setUrl(String url) { - this.url = url; - } + /** + * Returns the paths for the plugin's gdnative libraries (if any). + * + * The paths must be relative to the 'assets' directory and point to a '*.gdnlib' file. + */ + @NonNull + Set<String> getPluginGDNativeLibrariesPaths(); } 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..5b41205253 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java @@ -0,0 +1,172 @@ +/*************************************************************************/ +/* GodotPluginRegistry.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot.plugin; + +import org.godotengine.godot.Godot; + +import android.app.Activity; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.Nullable; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 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."; + + 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 { + final Activity activity = godot.getActivity(); + ApplicationInfo appInfo = activity + .getPackageManager() + .getApplicationInfo(activity.getPackageName(), + PackageManager.GET_META_DATA); + Bundle metaData = appInfo.metaData; + if (metaData == null || metaData.isEmpty()) { + return; + } + + 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(); + Log.i(TAG, "Initializing Godot plugin " + pluginName); + + // 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); + Log.i(TAG, "Completed initialization for Godot plugin " + pluginHandle.getPluginName()); + } 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/payments/PaymentsCache.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java index 84a7eda6e0..6428422641 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsCache.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/SignalInfo.java @@ -1,12 +1,12 @@ /*************************************************************************/ -/* PaymentsCache.java */ +/* SignalInfo.java */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,45 +28,72 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.payments; +package org.godotengine.godot.plugin; -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; +import android.text.TextUtils; -public class PaymentsCache { +import androidx.annotation.NonNull; - public Context context; +import java.util.Arrays; - public PaymentsCache(Context context) { - this.context = context; +/** + * Store information about a {@link GodotPlugin}'s signal. + */ +public final class SignalInfo { + private final String name; + private final Class<?>[] paramTypes; + private final String[] paramTypesNames; + + public SignalInfo(@NonNull String signalName, Class<?>... paramTypes) { + if (TextUtils.isEmpty(signalName)) { + throw new IllegalArgumentException("Invalid signal name: " + signalName); + } + + this.name = signalName; + this.paramTypes = paramTypes == null ? new Class<?>[ 0 ] : paramTypes; + this.paramTypesNames = new String[this.paramTypes.length]; + for (int i = 0; i < this.paramTypes.length; i++) { + this.paramTypesNames[i] = this.paramTypes[i].getName(); + } + } + + public String getName() { + return name; } - public void setConsumableFlag(String set, String sku, Boolean flag) { - SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPref.edit(); - editor.putBoolean(sku, flag); - editor.apply(); + Class<?>[] getParamTypes() { + return paramTypes; } - public boolean getConsumableFlag(String set, String sku) { - SharedPreferences sharedPref = context.getSharedPreferences( - "consumables_" + set, Context.MODE_PRIVATE); - return sharedPref.getBoolean(sku, false); + String[] getParamTypesNames() { + return paramTypesNames; } - public void setConsumableValue(String set, String sku, String value) { - SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPref.edit(); - editor.putString(sku, value); - //Log.d("XXX", "Setting asset: consumables_" + set + ":" + sku); - editor.apply(); + @Override + public String toString() { + return "SignalInfo{" + + + "name='" + name + '\'' + + ", paramsTypes=" + Arrays.toString(paramTypes) + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SignalInfo)) { + return false; + } + + SignalInfo that = (SignalInfo)o; + + return name.equals(that.name); } - public String getConsumableValue(String set, String sku) { - SharedPreferences sharedPref = context.getSharedPreferences( - "consumables_" + set, Context.MODE_PRIVATE); - //Log.d("XXX", "Getting asset: consumables_" + set + ":" + sku); - return sharedPref.getString(sku, null); + @Override + public int hashCode() { + return name.hashCode(); } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java b/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java index bc0e565774..d6e49bb635 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -34,7 +34,6 @@ import java.security.MessageDigest; import java.util.Random; public class Crypt { - public static String md5(String input) { try { // Create MD5 Hash diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java index bbf876ea1f..19588f8465 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,6 +31,7 @@ package org.godotengine.godot.utils; import android.util.Log; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; @@ -39,12 +40,10 @@ import javax.microedition.khronos.egl.EGLDisplay; * Contains GL utilities methods. */ public class GLUtils { - private static final String TAG = GLUtils.class.getSimpleName(); public static final boolean DEBUG = false; - public static boolean use_gl3 = false; public static boolean use_32 = false; public static boolean use_debug_opengl = false; diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java index 011d426c7e..721d7c65c6 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,10 +30,10 @@ package org.godotengine.godot.utils; +import android.app.Activity; import android.content.Context; import android.net.wifi.WifiManager; import android.util.Log; -import org.godotengine.godot.Godot; /** * This class handles Android-specific networking functions. @@ -41,11 +41,10 @@ import org.godotengine.godot.Godot; * to receive broadcast and multicast packets. */ public class GodotNetUtils { - /* A single, reference counted, multicast lock, or null if permission CHANGE_WIFI_MULTICAST_STATE is missing */ private WifiManager.MulticastLock multicastLock; - public GodotNetUtils(Godot p_activity) { + public GodotNetUtils(Activity p_activity) { if (PermissionsUtil.hasManifestPermission(p_activity, "android.permission.CHANGE_WIFI_MULTICAST_STATE")) { WifiManager wifi = (WifiManager)p_activity.getApplicationContext().getSystemService(Context.WIFI_SERVICE); multicastLock = wifi.createMulticastLock("GodotMulticastLock"); diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/HttpRequester.java b/platform/android/java/lib/src/org/godotengine/godot/utils/HttpRequester.java deleted file mode 100644 index 68f9a83597..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/HttpRequester.java +++ /dev/null @@ -1,227 +0,0 @@ -/*************************************************************************/ -/* HttpRequester.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.utils; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.security.KeyStore; -import java.util.Date; -import org.apache.http.HttpResponse; -import org.apache.http.HttpVersion; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.PlainSocketFactory; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; -import org.apache.http.params.HttpProtocolParams; -import org.apache.http.protocol.HTTP; -import org.apache.http.util.EntityUtils; - -/** - * - * @author Luis Linietsky <luis.linietsky@gmail.com> - */ -public class HttpRequester { - - private Context context; - private static final int TTL = 600000; // 10 minutos - private long cttl = 0; - - public HttpRequester() { - //Log.d("XXX", "Creando http request sin contexto"); - } - - public HttpRequester(Context context) { - this.context = context; - //Log.d("XXX", "Creando http request con contexto"); - } - - public String post(RequestParams params) { - HttpPost httppost = new HttpPost(params.getUrl()); - try { - httppost.setEntity(new UrlEncodedFormEntity(params.toPairsList())); - return request(httppost); - } catch (UnsupportedEncodingException e) { - return null; - } - } - - public String get(RequestParams params) { - String response = getResponseFromCache(params.getUrl()); - if (response == null) { - //Log.d("XXX", "Cache miss!"); - HttpGet httpget = new HttpGet(params.getUrl()); - long timeInit = new Date().getTime(); - response = request(httpget); - long delay = new Date().getTime() - timeInit; - Log.d("HttpRequest::get(url)", "Url: " + params.getUrl() + " downloaded in " + String.format("%.03f", delay / 1000.0f) + " seconds"); - if (response == null || response.length() == 0) { - response = ""; - } else { - saveResponseIntoCache(params.getUrl(), response); - } - } - Log.d("XXX", "Req: " + params.getUrl()); - Log.d("XXX", "Resp: " + response); - return response; - } - - private String request(HttpUriRequest request) { - //Log.d("XXX", "Haciendo request a: " + request.getURI() ); - Log.d("PPP", "Haciendo request a: " + request.getURI()); - long init = new Date().getTime(); - HttpClient httpclient = getNewHttpClient(); - HttpParams httpParameters = httpclient.getParams(); - HttpConnectionParams.setConnectionTimeout(httpParameters, 0); - HttpConnectionParams.setSoTimeout(httpParameters, 0); - HttpConnectionParams.setTcpNoDelay(httpParameters, true); - try { - HttpResponse response = httpclient.execute(request); - Log.d("PPP", "Fin de request (" + (new Date().getTime() - init) + ") a: " + request.getURI()); - //Log.d("XXX1", "Status:" + response.getStatusLine().toString()); - if (response.getStatusLine().getStatusCode() == 200) { - String strResponse = EntityUtils.toString(response.getEntity()); - //Log.d("XXX2", strResponse); - return strResponse; - } else { - Log.d("XXX3", "Response status code:" + response.getStatusLine().getStatusCode() + "\n" + EntityUtils.toString(response.getEntity())); - return null; - } - - } catch (ClientProtocolException e) { - Log.d("XXX3", e.getMessage()); - } catch (IOException e) { - Log.d("XXX4", e.getMessage()); - } - return null; - } - - private HttpClient getNewHttpClient() { - try { - KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - trustStore.load(null, null); - - SSLSocketFactory sf = new CustomSSLSocketFactory(trustStore); - sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); - - HttpParams params = new BasicHttpParams(); - HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); - HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); - - SchemeRegistry registry = new SchemeRegistry(); - registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); - registry.register(new Scheme("https", sf, 443)); - - ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry); - - return new DefaultHttpClient(ccm, params); - } catch (Exception e) { - return new DefaultHttpClient(); - } - } - - private static String convertStreamToString(InputStream is) { - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - StringBuilder sb = new StringBuilder(); - String line = null; - try { - while ((line = reader.readLine()) != null) { - sb.append((line + "\n")); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - is.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - return sb.toString(); - } - - public void saveResponseIntoCache(String request, String response) { - if (context == null) { - //Log.d("XXX", "No context, cache failed!"); - return; - } - SharedPreferences sharedPref = context.getSharedPreferences("http_get_cache", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPref.edit(); - editor.putString("request_" + Crypt.md5(request), response); - editor.putLong("request_" + Crypt.md5(request) + "_ttl", new Date().getTime() + getTtl()); - editor.apply(); - } - - public String getResponseFromCache(String request) { - if (context == null) { - Log.d("XXX", "No context, cache miss"); - return null; - } - SharedPreferences sharedPref = context.getSharedPreferences("http_get_cache", Context.MODE_PRIVATE); - long ttl = getResponseTtl(request); - if (ttl == 0l || (new Date().getTime() - ttl) > 0l) { - Log.d("XXX", "Cache invalid ttl:" + ttl + " vs now:" + new Date().getTime()); - return null; - } - return sharedPref.getString("request_" + Crypt.md5(request), null); - } - - public long getResponseTtl(String request) { - SharedPreferences sharedPref = context.getSharedPreferences( - "http_get_cache", Context.MODE_PRIVATE); - return sharedPref.getLong("request_" + Crypt.md5(request) + "_ttl", 0l); - } - - public long getTtl() { - return cttl > 0 ? cttl : TTL; - } - - public void setTtl(long ttl) { - this.cttl = (ttl * 1000) + new Date().getTime(); - } -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java index 7cf32b00fe..b0ca96271e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,20 +31,24 @@ package org.godotengine.godot.utils; import android.Manifest; +import android.app.Activity; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PermissionInfo; import android.os.Build; -import android.support.v4.content.ContextCompat; +import android.util.Log; + +import androidx.core.content.ContextCompat; + import java.util.ArrayList; import java.util.List; -import org.godotengine.godot.Godot; /** * This class includes utility functions for Android permissions related operations. * @author Cagdas Caglak <cagdascaglak@gmail.com> */ public final class PermissionsUtil { + private static final String TAG = PermissionsUtil.class.getSimpleName(); static final int REQUEST_RECORD_AUDIO_PERMISSION = 1; static final int REQUEST_CAMERA_PERMISSION = 2; @@ -60,7 +64,7 @@ public final class PermissionsUtil { * @param activity the caller activity for this method. * @return true/false. "true" if permission was granted otherwise returns "false". */ - public static boolean requestPermission(String name, Godot activity) { + public static boolean requestPermission(String name, Activity activity) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // Not necessary, asked on install already return true; @@ -88,7 +92,7 @@ public final class PermissionsUtil { * @param activity the caller activity for this method. * @return true/false. "true" if all permissions were granted otherwise returns "false". */ - public static boolean requestManifestPermissions(Godot activity) { + public static boolean requestManifestPermissions(Activity activity) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return true; } @@ -113,8 +117,8 @@ public final class PermissionsUtil { dangerousPermissions.add(manifestPermission); } } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - return false; + // Skip this permission and continue. + Log.w(TAG, "Unable to identify permission " + manifestPermission, e); } } @@ -133,7 +137,7 @@ public final class PermissionsUtil { * @param activity the caller activity for this method. * @return granted permissions list */ - public static String[] getGrantedPermissions(Godot activity) { + public static String[] getGrantedPermissions(Activity activity) { String[] manifestPermissions; try { manifestPermissions = getManifestPermissions(activity); @@ -153,8 +157,8 @@ public final class PermissionsUtil { dangerousPermissions.add(manifestPermission); } } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - return new String[0]; + // Skip this permission and continue. + Log.w(TAG, "Unable to identify permission " + manifestPermission, e); } } @@ -167,7 +171,7 @@ public final class PermissionsUtil { * @param permission the permession to look for in the manifest file. * @return "true" if the permission is in the manifest file of the activity, "false" otherwise. */ - public static boolean hasManifestPermission(Godot activity, String permission) { + public static boolean hasManifestPermission(Activity activity, String permission) { try { for (String p : getManifestPermissions(activity)) { if (permission.equals(p)) @@ -185,7 +189,7 @@ public final class PermissionsUtil { * @return manifest permissions list * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. */ - private static String[] getManifestPermissions(Godot activity) throws PackageManager.NameNotFoundException { + private static String[] getManifestPermissions(Activity activity) throws PackageManager.NameNotFoundException { PackageManager packageManager = activity.getPackageManager(); PackageInfo packageInfo = packageManager.getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS); if (packageInfo.requestedPermissions == null) @@ -200,7 +204,7 @@ public final class PermissionsUtil { * @return permission info object * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. */ - private static PermissionInfo getPermissionInfo(Godot activity, String permission) throws PackageManager.NameNotFoundException { + private static PermissionInfo getPermissionInfo(Activity activity, String permission) throws PackageManager.NameNotFoundException { PackageManager packageManager = activity.getPackageManager(); return packageManager.getPermissionInfo(permission, 0); } 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..a35f6ec5a7 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt @@ -0,0 +1,108 @@ +/*************************************************************************/ +/* VkRenderer.kt */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +@file:JvmName("VkRenderer") +package org.godotengine.godot.vulkan + +import android.view.Surface + +import org.godotengine.godot.Godot +import org.godotengine.godot.GodotLib +import org.godotengine.godot.plugin.GodotPlugin +import org.godotengine.godot.plugin.GodotPluginRegistry + +/** + * 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 { + private val pluginRegistry: GodotPluginRegistry = GodotPluginRegistry.getPluginRegistry() + + /** + * Called when the surface is created and signals the beginning of rendering. + */ + fun onVkSurfaceCreated(surface: Surface) { + GodotLib.newcontext(surface, false) + + for (plugin in pluginRegistry.getAllPlugins()) { + plugin.onVkSurfaceCreated(surface) + } + } + + /** + * Called after the surface is created and whenever its size changes. + */ + fun onVkSurfaceChanged(surface: Surface, width: Int, height: Int) { + GodotLib.resize(surface, width, height) + + for (plugin in pluginRegistry.getAllPlugins()) { + plugin.onVkSurfaceChanged(surface, width, height) + } + } + + /** + * Called to draw the current frame. + */ + fun onVkDrawFrame() { + GodotLib.step() + for (plugin in pluginRegistry.getAllPlugins()) { + plugin.onVkDrawFrame() + } + } + + /** + * Called when the rendering thread is resumed. + */ + fun onVkResume() { + GodotLib.onRendererResumed() + } + + /** + * Called when the rendering thread is paused. + */ + fun onVkPause() { + GodotLib.onRendererPaused() + } + + /** + * Called when the rendering thread is destroyed and used as signal to tear down the Vulkan logic. + */ + fun onVkDestroy() { + } +} 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..f0e37d80b8 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt @@ -0,0 +1,135 @@ +/*************************************************************************/ +/* VkSurfaceView.kt */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +@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> + */ +open 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. + */ + open fun onResume() { + vkThread.onResume() + } + + /** + * Pauses the rendering thread. + * + * Must not be called before a [VkRenderer] has been set. + */ + open 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..b967fd5f24 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt @@ -0,0 +1,228 @@ +/*************************************************************************/ +/* VkThread.kt */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +@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, "InterruptedException", ex) + } catch (ex: IllegalStateException) { + Log.i(TAG, "IllegalStateException", ex) + } finally { + threadExiting() + } + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/XRMode.java b/platform/android/java/lib/src/org/godotengine/godot/xr/XRMode.java index 982e43f9d1..0995477baf 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/XRMode.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/XRMode.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java index 9209d6ccf2..245d573bfc 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,6 +32,7 @@ package org.godotengine.godot.xr.ovr; import android.opengl.EGLExt; import android.opengl.GLSurfaceView; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; @@ -40,7 +41,6 @@ import javax.microedition.khronos.egl.EGLDisplay; * EGL config chooser for the Oculus Mobile VR SDK. */ public class OvrConfigChooser implements GLSurfaceView.EGLConfigChooser { - private static final int[] CONFIG_ATTRIBS = { EGL10.EGL_RED_SIZE, 8, EGL10.EGL_GREEN_SIZE, 8, diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java index 36f4416df2..d3aca267ef 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,6 +32,7 @@ package org.godotengine.godot.xr.ovr; import android.opengl.EGL14; import android.opengl.GLSurfaceView; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; @@ -41,7 +42,6 @@ import javax.microedition.khronos.egl.EGLDisplay; * EGL Context factory for the Oculus mobile VR SDK. */ public class OvrContextFactory implements GLSurfaceView.EGLContextFactory { - private static final int[] CONTEXT_ATTRIBS = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE }; diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java index b2aa130f37..83aa458064 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,6 +31,7 @@ package org.godotengine.godot.xr.ovr; import android.opengl.GLSurfaceView; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; @@ -40,7 +41,6 @@ import javax.microedition.khronos.egl.EGLSurface; * EGL window surface factory for the Oculus mobile VR SDK. */ public class OvrWindowSurfaceFactory implements GLSurfaceView.EGLWindowSurfaceFactory { - private final static int[] SURFACE_ATTRIBS = { EGL10.EGL_WIDTH, 16, EGL10.EGL_HEIGHT, 16, diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java index ce4defd7a7..341427209b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,21 +30,24 @@ package org.godotengine.godot.xr.regular; +import org.godotengine.godot.utils.GLUtils; + import android.opengl.GLSurfaceView; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; -import org.godotengine.godot.utils.GLUtils; /** * Used to select the egl config for pancake games. */ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser { - private static final String TAG = RegularConfigChooser.class.getSimpleName(); private int[] mValue = new int[1]; + // FIXME: Add support for Vulkan. + /* This EGL config specification is used to specify 2.0 rendering. * We use a minimum size of 4 bits for red/green/blue, but will * perform actual matching in chooseConfig() below. @@ -59,15 +62,6 @@ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser { EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL10.EGL_NONE }; - private static int[] s_configAttribs3 = { - EGL10.EGL_RED_SIZE, 4, - EGL10.EGL_GREEN_SIZE, 4, - EGL10.EGL_BLUE_SIZE, 4, - // EGL10.EGL_DEPTH_SIZE, 16, - // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE, - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //apparently there is no EGL_OPENGL_ES3_BIT - EGL10.EGL_NONE - }; public RegularConfigChooser(int r, int g, int b, int a, int depth, int stencil) { mRedSize = r; @@ -79,11 +73,10 @@ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser { } public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { - /* Get the number of minimally matching EGL configurations */ int[] num_config = new int[1]; - egl.eglChooseConfig(display, GLUtils.use_gl3 ? s_configAttribs3 : s_configAttribs2, null, 0, num_config); + egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config); int numConfigs = num_config[0]; @@ -94,7 +87,7 @@ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser { /* Allocate then read the array of minimally matching EGL configs */ EGLConfig[] configs = new EGLConfig[numConfigs]; - egl.eglChooseConfig(display, GLUtils.use_gl3 ? s_configAttribs3 : s_configAttribs2, configs, numConfigs, num_config); + egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config); if (GLUtils.DEBUG) { GLUtils.printConfigs(egl, display, configs); @@ -134,7 +127,6 @@ public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser { private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attribute, int defaultValue) { - if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { return mValue[0]; } diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java index 22bd4ced87..71610d2d00 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,14 +30,16 @@ package org.godotengine.godot.xr.regular; +import org.godotengine.godot.GodotLib; +import org.godotengine.godot.utils.GLUtils; + import android.opengl.GLSurfaceView; import android.util.Log; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; -import org.godotengine.godot.GodotLib; -import org.godotengine.godot.utils.GLUtils; /** * Factory used to setup the opengl context for pancake games. @@ -51,25 +53,17 @@ public class RegularContextFactory implements GLSurfaceView.EGLContextFactory { private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098; public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { - String driver_name = GodotLib.getGlobal("rendering/quality/driver/driver_name"); - if (GLUtils.use_gl3 && !driver_name.equals("GLES3")) { - GLUtils.use_gl3 = false; - } - if (GLUtils.use_gl3) - Log.w(TAG, "creating OpenGL ES 3.0 context :"); - else - Log.w(TAG, "creating OpenGL ES 2.0 context :"); + // FIXME: Add support for Vulkan. + Log.w(TAG, "creating OpenGL ES 2.0 context :"); GLUtils.checkEglError(TAG, "Before eglCreateContext", egl); EGLContext context; if (GLUtils.use_debug_opengl) { int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE }; - int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE }; - context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, GLUtils.use_gl3 ? attrib_list3 : attrib_list2); + context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list2); } else { int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; - int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE }; - context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, GLUtils.use_gl3 ? attrib_list3 : attrib_list2); + context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list2); } GLUtils.checkEglError(TAG, "After eglCreateContext", egl); return context; diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java index 71fcf06020..e690c5b695 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,15 +30,16 @@ package org.godotengine.godot.xr.regular; +import org.godotengine.godot.utils.GLUtils; + import android.util.Log; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; -import org.godotengine.godot.utils.GLUtils; /* Fallback if 32bit View is not supported*/ public class RegularFallbackConfigChooser extends RegularConfigChooser { - private static final String TAG = RegularFallbackConfigChooser.class.getSimpleName(); private RegularConfigChooser fallback; diff --git a/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml b/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml new file mode 100644 index 0000000000..dc180375d5 --- /dev/null +++ b/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest package="org.godotengine.godot" /> diff --git a/platform/android/java/lib/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt index d3bdf6a5f2..34925684da 100644 --- a/platform/android/java/lib/CMakeLists.txt +++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt @@ -1,3 +1,4 @@ +# Non functional cmake build file used to provide Android Studio editor support to the project. cmake_minimum_required(VERSION 3.6) project(godot) diff --git a/platform/android/java/nativeSrcsConfigs/README.md b/platform/android/java/nativeSrcsConfigs/README.md new file mode 100644 index 0000000000..e48505ccda --- /dev/null +++ b/platform/android/java/nativeSrcsConfigs/README.md @@ -0,0 +1,4 @@ +## Native sources configs + +This is a non functional Android library used to provide Android Studio editor support to the Godot project native files. +Nothing else should be added to this library. diff --git a/platform/android/java/nativeSrcsConfigs/build.gradle b/platform/android/java/nativeSrcsConfigs/build.gradle new file mode 100644 index 0000000000..66077060ea --- /dev/null +++ b/platform/android/java/nativeSrcsConfigs/build.gradle @@ -0,0 +1,56 @@ +// Non functional android library used to provide Android Studio editor support to the project. +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion versions.compileSdk + buildToolsVersion versions.buildTools + + defaultConfig { + minSdkVersion versions.minSdk + targetSdkVersion versions.targetSdk + } + + compileOptions { + sourceCompatibility versions.javaVersion + targetCompatibility versions.javaVersion + } + + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + + // Should be uncommented for development purpose within Android Studio + // doNotStrip '**/*.so' + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + } + } + + ndkVersion versions.ndkVersion + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + + libraryVariants.all { variant -> + def buildType = variant.buildType.name.capitalize() + + def taskPrefix = "" + if (project.path != ":") { + taskPrefix = project.path + ":" + } + + // Disable the externalNativeBuild* task as it would cause build failures since the cmake build + // files is only setup for editing support. + gradle.startParameter.excludedTaskNames += taskPrefix + "externalNativeBuild" + buildType + } +} + +dependencies {} diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle index f6921c70aa..524031d93f 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 ':nativeSrcsConfigs' diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index feebea2738..ab03599dc3 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,36 +28,32 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "java_class_wrapper.h" +#include "api/java_class_wrapper.h" #include "string_android.h" #include "thread_jandroid.h" -bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error, Variant &ret) { - - Map<StringName, List<MethodInfo> >::Element *M = methods.find(p_method); +bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret) { + Map<StringName, List<MethodInfo>>::Element *M = methods.find(p_method); if (!M) return false; - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); - MethodInfo *method = NULL; + MethodInfo *method = nullptr; for (List<MethodInfo>::Element *E = M->get().front(); E; E = E->next()) { - if (!p_instance && !E->get()._static) { - r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL; + r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; continue; } int pc = E->get().param_types.size(); if (pc > p_argcount) { - - r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; r_error.argument = pc; continue; } if (pc < p_argcount) { - - r_error.error = Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_error.argument = pc; continue; } @@ -65,10 +61,8 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, bool valid = true; for (int i = 0; i < pc; i++) { - Variant::Type arg_expected = Variant::NIL; switch (ptypes[i]) { - case ARG_TYPE_VOID: { //bug? } break; @@ -86,7 +80,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, case ARG_TYPE_SHORT: case ARG_TYPE_INT: case ARG_TYPE_LONG: { - if (!p_args[i]->is_num()) arg_expected = Variant::INT; @@ -95,32 +88,26 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, case ARG_NUMBER_CLASS_BIT | ARG_TYPE_DOUBLE: case ARG_TYPE_FLOAT: case ARG_TYPE_DOUBLE: { - if (!p_args[i]->is_num()) - arg_expected = Variant::REAL; + arg_expected = Variant::FLOAT; } break; case ARG_TYPE_STRING: { - if (p_args[i]->get_type() != Variant::STRING) arg_expected = Variant::STRING; } break; case ARG_TYPE_CLASS: { - if (p_args[i]->get_type() != Variant::OBJECT) arg_expected = Variant::OBJECT; else { - Ref<Reference> ref = *p_args[i]; if (!ref.is_null()) { if (Object::cast_to<JavaObject>(ref.ptr())) { - Ref<JavaObject> jo = ref; //could be faster jclass c = env->FindClass(E->get().param_sigs[i].operator String().utf8().get_data()); if (!c || !env->IsInstanceOf(jo->instance, c)) { - arg_expected = Variant::OBJECT; } else { //ok @@ -133,7 +120,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; default: { - if (p_args[i]->get_type() != Variant::ARRAY) arg_expected = Variant::ARRAY; @@ -141,7 +127,7 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } if (arg_expected != Variant::NIL) { - r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = i; r_error.expected = arg_expected; valid = false; @@ -158,22 +144,20 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, if (!method) return true; //no version convinces - r_error.error = Variant::CallError::CALL_OK; + r_error.error = Callable::CallError::CALL_OK; - jvalue *argv = NULL; + jvalue *argv = nullptr; if (method->param_types.size()) { - argv = (jvalue *)alloca(sizeof(jvalue) * method->param_types.size()); } List<jobject> to_free; for (int i = 0; i < method->param_types.size(); i++) { - switch (method->param_types[i]) { case ARG_TYPE_VOID: { //can't happen - argv[i].l = NULL; //I hope this works + argv[i].l = nullptr; //I hope this works } break; case ARG_TYPE_BOOLEAN: { @@ -279,18 +263,15 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, to_free.push_back(jStr); } break; case ARG_TYPE_CLASS: { - Ref<JavaObject> jo = *p_args[i]; if (jo.is_valid()) { - argv[i].l = jo->instance; } else { - argv[i].l = NULL; //I hope this works + argv[i].l = nullptr; //I hope this works } } break; case ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: { - Array arr = *p_args[i]; jbooleanArray a = env->NewBooleanArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -302,7 +283,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_BYTE: { - Array arr = *p_args[i]; jbyteArray a = env->NewByteArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -314,7 +294,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_CHAR: { - Array arr = *p_args[i]; jcharArray a = env->NewCharArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -326,7 +305,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_SHORT: { - Array arr = *p_args[i]; jshortArray a = env->NewShortArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -338,7 +316,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_INT: { - Array arr = *p_args[i]; jintArray a = env->NewIntArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -360,7 +337,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_FLOAT: { - Array arr = *p_args[i]; jfloatArray a = env->NewFloatArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -372,7 +348,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_DOUBLE: { - Array arr = *p_args[i]; jdoubleArray a = env->NewDoubleArray(arr.size()); for (int j = 0; j < arr.size(); j++) { @@ -384,11 +359,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_ARRAY_BIT | ARG_TYPE_STRING: { - Array arr = *p_args[i]; - jobjectArray a = env->NewObjectArray(arr.size(), env->FindClass("java/lang/String"), NULL); + jobjectArray a = env->NewObjectArray(arr.size(), env->FindClass("java/lang/String"), nullptr); for (int j = 0; j < arr.size(); j++) { - String s = arr[j]; jstring jStr = env->NewStringUTF(s.utf8().get_data()); env->SetObjectArrayElement(a, j, jStr); @@ -399,17 +372,15 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, to_free.push_back(a); } break; case ARG_ARRAY_BIT | ARG_TYPE_CLASS: { - - argv[i].l = NULL; + argv[i].l = nullptr; } break; } } - r_error.error = Variant::CallError::CALL_OK; + r_error.error = Callable::CallError::CALL_OK; bool success = true; switch (method->return_type) { - case ARG_TYPE_VOID: { if (method->_static) { env->CallStaticVoidMethodA(_class, method->method, argv); @@ -434,7 +405,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } } break; case ARG_TYPE_CHAR: { - if (method->_static) { ret = env->CallStaticCharMethodA(_class, method->method, argv); } else { @@ -442,7 +412,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } } break; case ARG_TYPE_SHORT: { - if (method->_static) { ret = env->CallStaticShortMethodA(_class, method->method, argv); } else { @@ -451,7 +420,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_TYPE_INT: { - if (method->_static) { ret = env->CallStaticIntMethodA(_class, method->method, argv); } else { @@ -460,7 +428,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_TYPE_LONG: { - if (method->_static) { ret = (int64_t)env->CallStaticLongMethodA(_class, method->method, argv); } else { @@ -469,7 +436,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_TYPE_FLOAT: { - if (method->_static) { ret = env->CallStaticFloatMethodA(_class, method->method, argv); } else { @@ -478,7 +444,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; case ARG_TYPE_DOUBLE: { - if (method->_static) { ret = env->CallStaticDoubleMethodA(_class, method->method, argv); } else { @@ -487,7 +452,6 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; default: { - jobject obj; if (method->_static) { obj = env->CallStaticObjectMethodA(_class, method->method, argv); @@ -498,10 +462,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, if (!obj) { ret = Variant(); } else { - if (!_convert_object_to_variant(env, obj, ret, method->return_type)) { ret = Variant(); - r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; success = false; } env->DeleteLocalRef(obj); @@ -517,10 +480,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, return success; } -Variant JavaClass::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { - +Variant JavaClass::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { Variant ret; - bool found = _call_method(NULL, p_method, p_args, p_argcount, r_error, ret); + bool found = _call_method(nullptr, p_method, p_args, p_argcount, r_error, ret); if (found) { return ret; } @@ -533,8 +495,7 @@ JavaClass::JavaClass() { ///////////////////// -Variant JavaObject::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { - +Variant JavaObject::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { return Variant(); } @@ -546,20 +507,13 @@ JavaObject::~JavaObject() { //////////////////// -void JavaClassWrapper::_bind_methods() { - - ClassDB::bind_method(D_METHOD("wrap", "name"), &JavaClassWrapper::wrap); -} - bool JavaClassWrapper::_get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig) { - jstring name2 = (jstring)env->CallObjectMethod(obj, Class_getName); String str_type = jstring_to_string(name2, env); env->DeleteLocalRef(name2); uint32_t t = 0; if (str_type.begins_with("[")) { - t = JavaClass::ARG_ARRAY_BIT; strsig = "["; str_type = str_type.substr(1, str_type.length() - 1); @@ -638,87 +592,71 @@ bool JavaClassWrapper::_get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, St } bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &var, uint32_t p_sig) { - if (!obj) { var = Variant(); //seems null is just null... return true; } switch (p_sig) { - case ARG_TYPE_VOID: { - return Variant(); } break; case ARG_TYPE_BOOLEAN | ARG_NUMBER_CLASS_BIT: { - var = env->CallBooleanMethod(obj, JavaClassWrapper::singleton->Boolean_booleanValue); return true; } break; case ARG_TYPE_BYTE | ARG_NUMBER_CLASS_BIT: { - var = env->CallByteMethod(obj, JavaClassWrapper::singleton->Byte_byteValue); return true; } break; case ARG_TYPE_CHAR | ARG_NUMBER_CLASS_BIT: { - var = env->CallCharMethod(obj, JavaClassWrapper::singleton->Character_characterValue); return true; } break; case ARG_TYPE_SHORT | ARG_NUMBER_CLASS_BIT: { - var = env->CallShortMethod(obj, JavaClassWrapper::singleton->Short_shortValue); return true; } break; case ARG_TYPE_INT | ARG_NUMBER_CLASS_BIT: { - var = env->CallIntMethod(obj, JavaClassWrapper::singleton->Integer_integerValue); return true; } break; case ARG_TYPE_LONG | ARG_NUMBER_CLASS_BIT: { - var = (int64_t)env->CallLongMethod(obj, JavaClassWrapper::singleton->Long_longValue); return true; } break; case ARG_TYPE_FLOAT | ARG_NUMBER_CLASS_BIT: { - var = env->CallFloatMethod(obj, JavaClassWrapper::singleton->Float_floatValue); return true; } break; case ARG_TYPE_DOUBLE | ARG_NUMBER_CLASS_BIT: { - var = env->CallDoubleMethod(obj, JavaClassWrapper::singleton->Double_doubleValue); return true; } break; case ARG_TYPE_STRING: { - var = jstring_to_string((jstring)obj, env); return true; } break; case ARG_TYPE_CLASS: { - return false; } break; case ARG_ARRAY_BIT | ARG_TYPE_VOID: { - var = Array(); // ? return true; } break; case ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jboolean val; env->GetBooleanArrayRegion((jbooleanArray)arr, 0, 1, &val); ret.push_back(val); @@ -729,14 +667,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va } break; case ARG_ARRAY_BIT | ARG_TYPE_BYTE: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jbyte val; env->GetByteArrayRegion((jbyteArray)arr, 0, 1, &val); ret.push_back(val); @@ -752,7 +688,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jchar val; env->GetCharArrayRegion((jcharArray)arr, 0, 1, &val); ret.push_back(val); @@ -768,7 +703,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jshort val; env->GetShortArrayRegion((jshortArray)arr, 0, 1, &val); ret.push_back(val); @@ -784,7 +718,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jint val; env->GetIntArrayRegion((jintArray)arr, 0, 1, &val); ret.push_back(val); @@ -800,7 +733,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jlong val; env->GetLongArrayRegion((jlongArray)arr, 0, 1, &val); ret.push_back((int64_t)val); @@ -816,7 +748,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jfloat val; env->GetFloatArrayRegion((jfloatArray)arr, 0, 1, &val); ret.push_back(val); @@ -832,7 +763,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jdouble val; env->GetDoubleArrayRegion((jdoubleArray)arr, 0, 1, &val); ret.push_back(val); @@ -842,14 +772,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_BOOLEAN: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -865,14 +793,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_BYTE: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -887,14 +813,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_CHAR: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -909,14 +833,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_SHORT: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -931,14 +853,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_INT: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -953,14 +873,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_LONG: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -975,14 +893,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_NUMBER_CLASS_BIT | ARG_ARRAY_BIT | ARG_TYPE_FLOAT: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -1003,7 +919,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -1019,14 +934,12 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va } break; case ARG_ARRAY_BIT | ARG_TYPE_STRING: { - Array ret; jobjectArray arr = (jobjectArray)obj; int count = env->GetArrayLength(arr); for (int i = 0; i < count; i++) { - jobject o = env->GetObjectArrayElement(arr, i); if (!o) ret.push_back(Variant()); @@ -1041,7 +954,6 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_ARRAY_BIT | ARG_TYPE_CLASS: { - } break; } @@ -1049,11 +961,10 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va } Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { - if (class_cache.has(p_class)) return class_cache[p_class]; - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jclass bclass = env->FindClass(p_class.utf8().get_data()); ERR_FAIL_COND_V(!bclass, Ref<JavaClass>()); @@ -1071,7 +982,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { int count = env->GetArrayLength(methods); for (int i = 0; i < count; i++) { - jobject obj = env->GetObjectArrayElement(methods, i); ERR_CONTINUE(!obj); @@ -1101,7 +1011,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { String signature = "("; for (int j = 0; j < count2; j++) { - jobject obj2 = env->GetObjectArrayElement(param_types, j); String strsig; uint32_t sig = 0; @@ -1143,7 +1052,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { bool discard = false; for (List<JavaClass::MethodInfo>::Element *E = java_class->methods[str_method].front(); E; E = E->next()) { - float new_likeliness = 0; float existing_likeliness = 0; @@ -1151,7 +1059,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { continue; bool valid = true; for (int j = 0; j < E->get().param_types.size(); j++) { - Variant::Type _new; float new_l; Variant::Type existing; @@ -1200,7 +1107,6 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { count = env->GetArrayLength(fields); for (int i = 0; i < count; i++) { - jobject obj = env->GetObjectArrayElement(fields, i); ERR_CONTINUE(!obj); @@ -1210,19 +1116,15 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { int mods = env->CallIntMethod(obj, Field_getModifiers); if ((mods & 0x8) && (mods & 0x10) && (mods & 0x1)) { //static final public! - jobject objc = env->CallObjectMethod(obj, Field_get, NULL); + jobject objc = env->CallObjectMethod(obj, Field_get, nullptr); if (objc) { - uint32_t sig; String strsig; jclass cl = env->GetObjectClass(objc); if (JavaClassWrapper::_get_type_sig(env, cl, sig, strsig)) { - if ((sig & JavaClass::ARG_TYPE_MASK) <= JavaClass::ARG_TYPE_STRING) { - Variant value; if (JavaClass::_convert_object_to_variant(env, objc, value, sig)) { - java_class->constant_map[str_field] = value; } } @@ -1241,15 +1143,14 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { return Ref<JavaClass>(); } -JavaClassWrapper *JavaClassWrapper::singleton = NULL; +JavaClassWrapper *JavaClassWrapper::singleton = nullptr; JavaClassWrapper::JavaClassWrapper(jobject p_activity) { - singleton = this; - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); - jclass activityClass = env->FindClass("org/godotengine/godot/Godot"); + jclass activityClass = env->FindClass("android/app/Activity"); jmethodID getClassLoader = env->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); classLoader = env->CallObjectMethod(p_activity, getClassLoader); classLoader = (jclass)env->NewGlobalRef(classLoader); diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 671d1072ea..41201db32b 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,12 +29,12 @@ /*************************************************************************/ #include "java_godot_io_wrapper.h" -#include "core/error_list.h" +#include "core/error/error_list.h" // JNIEnv is only valid within the thread it belongs to, in a multi threading environment // we can't cache it. // For GodotIO we call all access methods from our thread and we thus get a valid JNIEnv -// from ThreadAndroid. +// from get_jni_env(). GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instance) { godot_io_instance = p_env->NewGlobalRef(p_godot_io_instance); @@ -52,15 +52,13 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc _get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;"); _get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;"); _get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I"); + _screen_get_usable_rect = p_env->GetMethodID(cls, "screenGetUsableRect", "()[I"), _get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;"); - _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;)V"); + _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;ZIII)V"); _hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V"); _set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V"); + _get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I"); _get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(I)Ljava/lang/String;"); - _play_video = p_env->GetMethodID(cls, "playVideo", "(Ljava/lang/String;)V"); - _is_video_playing = p_env->GetMethodID(cls, "isVideoPlaying", "()Z"); - _pause_video = p_env->GetMethodID(cls, "pauseVideo", "()V"); - _stop_video = p_env->GetMethodID(cls, "stopVideo", "()V"); } } @@ -74,7 +72,7 @@ jobject GodotIOJavaWrapper::get_instance() { Error GodotIOJavaWrapper::open_uri(const String &p_uri) { if (_open_URI) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring jStr = env->NewStringUTF(p_uri.utf8().get_data()); return env->CallIntMethod(godot_io_instance, _open_URI, jStr) ? ERR_CANT_OPEN : OK; } else { @@ -84,7 +82,7 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) { String GodotIOJavaWrapper::get_user_data_dir() { if (_get_data_dir) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir); return jstring_to_string(s, env); } else { @@ -94,7 +92,7 @@ String GodotIOJavaWrapper::get_user_data_dir() { String GodotIOJavaWrapper::get_locale() { if (_get_locale) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_locale); return jstring_to_string(s, env); } else { @@ -104,7 +102,7 @@ String GodotIOJavaWrapper::get_locale() { String GodotIOJavaWrapper::get_model() { if (_get_model) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_model); return jstring_to_string(s, env); } else { @@ -114,16 +112,29 @@ String GodotIOJavaWrapper::get_model() { int GodotIOJavaWrapper::get_screen_dpi() { if (_get_screen_DPI) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); return env->CallIntMethod(godot_io_instance, _get_screen_DPI); } else { return 160; } } +void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) { + if (_screen_get_usable_rect) { + JNIEnv *env = get_jni_env(); + jintArray returnArray = (jintArray)env->CallObjectMethod(godot_io_instance, _screen_get_usable_rect); + ERR_FAIL_COND(env->GetArrayLength(returnArray) != 4); + jint *arrayBody = env->GetIntArrayElements(returnArray, JNI_FALSE); + for (int i = 0; i < 4; i++) { + p_rect_xywh[i] = arrayBody[i]; + } + env->ReleaseIntArrayElements(returnArray, arrayBody, 0); + } +} + String GodotIOJavaWrapper::get_unique_id() { if (_get_unique_id) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_unique_id); return jstring_to_string(s, env); } else { @@ -135,31 +146,40 @@ bool GodotIOJavaWrapper::has_vk() { return (_show_keyboard != 0) && (_hide_keyboard != 0); } -void GodotIOJavaWrapper::show_vk(const String &p_existing) { +void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { if (_show_keyboard) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring jStr = env->NewStringUTF(p_existing.utf8().get_data()); - env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr); + env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end); } } void GodotIOJavaWrapper::hide_vk() { if (_hide_keyboard) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(godot_io_instance, _hide_keyboard); } } void GodotIOJavaWrapper::set_screen_orientation(int p_orient) { if (_set_screen_orientation) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(godot_io_instance, _set_screen_orientation, p_orient); } } +int GodotIOJavaWrapper::get_screen_orientation() { + if (_get_screen_orientation) { + JNIEnv *env = get_jni_env(); + return env->CallIntMethod(godot_io_instance, _get_screen_orientation); + } else { + return 0; + } +} + String GodotIOJavaWrapper::get_system_dir(int p_dir) { if (_get_system_dir) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir); return jstring_to_string(s, env); } else { @@ -167,41 +187,14 @@ String GodotIOJavaWrapper::get_system_dir(int p_dir) { } } -void GodotIOJavaWrapper::play_video(const String &p_path) { - // Why is this not here?!?! -} - -bool GodotIOJavaWrapper::is_video_playing() { - if (_is_video_playing) { - JNIEnv *env = ThreadAndroid::get_env(); - return env->CallBooleanMethod(godot_io_instance, _is_video_playing); - } else { - return false; - } -} - -void GodotIOJavaWrapper::pause_video() { - if (_pause_video) { - JNIEnv *env = ThreadAndroid::get_env(); - env->CallVoidMethod(godot_io_instance, _pause_video); - } -} - -void GodotIOJavaWrapper::stop_video() { - if (_stop_video) { - JNIEnv *env = ThreadAndroid::get_env(); - env->CallVoidMethod(godot_io_instance, _stop_video); - } -} - -// volatile because it can be changed from non-main thread and we need to +// SafeNumeric because it can be changed from non-main thread and we need to // ensure the change is immediately visible to other threads. -static volatile int virtual_keyboard_height; +static SafeNumeric<int> virtual_keyboard_height; int GodotIOJavaWrapper::get_vk_height() { - return virtual_keyboard_height; + return virtual_keyboard_height.get(); } void GodotIOJavaWrapper::set_vk_height(int p_height) { - virtual_keyboard_height = p_height; + virtual_keyboard_height.set(p_height); } diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h index 9fa6f2e469..394e97effa 100644 --- a/platform/android/java_godot_io_wrapper.h +++ b/platform/android/java_godot_io_wrapper.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -50,15 +50,13 @@ private: jmethodID _get_locale = 0; jmethodID _get_model = 0; jmethodID _get_screen_DPI = 0; + jmethodID _screen_get_usable_rect = 0; jmethodID _get_unique_id = 0; jmethodID _show_keyboard = 0; jmethodID _hide_keyboard = 0; jmethodID _set_screen_orientation = 0; + jmethodID _get_screen_orientation = 0; jmethodID _get_system_dir = 0; - jmethodID _play_video = 0; - jmethodID _is_video_playing = 0; - jmethodID _pause_video = 0; - jmethodID _stop_video = 0; public: GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instance); @@ -71,18 +69,16 @@ public: String get_locale(); String get_model(); int get_screen_dpi(); + void screen_get_usable_rect(int (&p_rect_xywh)[4]); String get_unique_id(); bool has_vk(); - void show_vk(const String &p_existing); + void show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end); void hide_vk(); int get_vk_height(); void set_vk_height(int p_height); void set_screen_orientation(int p_orient); + int get_screen_orientation(); String get_system_dir(int p_dir); - void play_video(const String &p_path); - bool is_video_playing(); - void pause_video(); - void stop_video(); }; #endif /* !JAVA_GODOT_IO_WRAPPER_H */ diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 858ff89cbc..bb22162879 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,568 +29,36 @@ /*************************************************************************/ #include "java_godot_lib_jni.h" + #include "java_godot_io_wrapper.h" #include "java_godot_wrapper.h" #include "android/asset_manager_jni.h" +#include "api/java_class_wrapper.h" +#include "api/jni_singleton.h" #include "audio_driver_jandroid.h" -#include "core/engine.h" -#include "core/os/keyboard.h" -#include "core/project_settings.h" +#include "core/config/engine.h" +#include "core/config/project_settings.h" +#include "core/input/input.h" #include "dir_access_jandroid.h" +#include "display_server_android.h" #include "file_access_android.h" -#include "file_access_jandroid.h" -#include "java_class_wrapper.h" -#include "main/input_default.h" +#include "jni_utils.h" #include "main/main.h" #include "net_socket_android.h" #include "os_android.h" #include "string_android.h" #include "thread_jandroid.h" -#include <unistd.h> - -static JavaClassWrapper *java_class_wrapper = NULL; -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::REAL: { - - 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::POOL_STRING_ARRAY: { - - PoolVector<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::POOL_INT_ARRAY: { - - PoolVector<int> array = *p_arg; - jintArray arr = env->NewIntArray(array.size()); - PoolVector<int>::Read r = array.read(); - env->SetIntArrayRegion(arr, 0, array.size(), r.ptr()); - v.val.l = arr; - v.obj = arr; - - } break; - case Variant::POOL_BYTE_ARRAY: { - PoolVector<uint8_t> array = *p_arg; - jbyteArray arr = env->NewByteArray(array.size()); - PoolVector<uint8_t>::Read r = array.read(); - env->SetByteArrayRegion(arr, 0, array.size(), reinterpret_cast<const signed char *>(r.ptr())); - v.val.l = arr; - v.obj = arr; - - } break; - case Variant::POOL_REAL_ARRAY: { - - PoolVector<float> array = *p_arg; - jfloatArray arr = env->NewFloatArray(array.size()); - PoolVector<float>::Read r = array.read(); - env->SetFloatArrayRegion(arr, 0, array.size(), r.ptr()); - v.val.l = arr; - v.obj = arr; - - } break; - 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); - PoolVector<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); - PoolVector<int> sarr; - sarr.resize(fCount); - - PoolVector<int>::Write w = sarr.write(); - env->GetIntArrayRegion(arr, 0, fCount, w.ptr()); - w.release(); - return sarr; - }; - - if (name == "[B") { - - jbyteArray arr = (jbyteArray)obj; - int fCount = env->GetArrayLength(arr); - PoolVector<uint8_t> sarr; - sarr.resize(fCount); - - PoolVector<uint8_t>::Write w = sarr.write(); - 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); - PoolRealArray sarr; - sarr.resize(fCount); - - PoolRealArray::Write w = sarr.write(); - - 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); - PoolRealArray sarr; - sarr.resize(fCount); - - PoolRealArray::Write w = sarr.write(); - - 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); - - PoolStringArray 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, Variant::CallError &r_error) { - - ERR_FAIL_COND_V(!instance, Variant()); - - r_error.error = Variant::CallError::CALL_OK; - - Map<StringName, MethodData>::Element *E = method_map.find(p_method); - if (!E) { - - r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; - return Variant(); - } - - int ac = E->get().argtypes.size(); - if (ac < p_argcount) { - - r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = ac; - return Variant(); - } - - if (ac > p_argcount) { - - r_error.error = Variant::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 = Variant::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::REAL: { - - 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::POOL_STRING_ARRAY: { - - jobjectArray arr = (jobjectArray)env->CallObjectMethodA(instance, E->get().method, v); - - ret = _jobject_to_variant(env, arr); - - env->DeleteLocalRef(arr); - } break; - case Variant::POOL_INT_ARRAY: { - - jintArray arr = (jintArray)env->CallObjectMethodA(instance, E->get().method, v); - int fCount = env->GetArrayLength(arr); - PoolVector<int> sarr; - sarr.resize(fCount); - - PoolVector<int>::Write w = sarr.write(); - env->GetIntArrayRegion(arr, 0, fCount, w.ptr()); - w.release(); - ret = sarr; - env->DeleteLocalRef(arr); - } break; - case Variant::POOL_REAL_ARRAY: { - - jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance, E->get().method, v); - - int fCount = env->GetArrayLength(arr); - PoolVector<float> sarr; - sarr.resize(fCount); - - PoolVector<float>::Write w = sarr.write(); - env->GetFloatArrayRegion(arr, 0, fCount, w.ptr()); - w.release(); - ret = sarr; - env->DeleteLocalRef(arr); - } break; - - 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 { +#include <android/input.h> +#include <unistd.h> - int a; - TST() { +#include <android/native_window_jni.h> - a = 5; - } -}; - -TST tst; +static JavaClassWrapper *java_class_wrapper = nullptr; +static OS_Android *os_android = nullptr; +static GodotJavaWrapper *godot_java = nullptr; +static GodotIOJavaWrapper *godot_io_java = nullptr; static bool initialized = false; static int step = 0; @@ -600,39 +68,30 @@ 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 godot_instance, jobject p_asset_manager, jboolean p_use_apk_expansion) { initialized = true; JavaVM *jvm; env->GetJavaVM(&jvm); // create our wrapper classes - godot_java = new GodotJavaWrapper(env, activity); // our activity is our godot instance is our activity.. + godot_java = new GodotJavaWrapper(env, activity, godot_instance); godot_io_java = new GodotIOJavaWrapper(env, godot_java->get_member_object("io", "Lorg/godotengine/godot/GodotIO;", env)); - ThreadAndroid::make_default(jvm); -#ifdef USE_JAVA_FILE_ACCESS - FileAccessJAndroid::setup(godot_io_java->get_instance()); -#else + init_thread_jandroid(jvm, env); jobject amgr = env->NewGlobalRef(p_asset_manager); FileAccessAndroid::asset_manager = AAssetManager_fromJava(env, amgr); -#endif DirAccessJAndroid::setup(godot_io_java->get_instance()); AudioDriverAndroid::setup(godot_io_java->get_instance()); @@ -646,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) { // lets cleanup if (godot_io_java) { delete godot_io_java; @@ -659,61 +118,20 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env } } -static void _initialize_java_modules() { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline) { + setup_android_thread(); - 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) { - ThreadAndroid::setup_thread(); - - const char **cmdline = NULL; - jstring *j_cmdline = NULL; + const char **cmdline = nullptr; + jstring *j_cmdline = nullptr; int cmdlen = 0; if (p_cmdline) { cmdlen = env->GetArrayLength(p_cmdline); if (cmdlen) { cmdline = (const char **)malloc((cmdlen + 1) * sizeof(const char *)); - cmdline[cmdlen] = NULL; + cmdline[cmdlen] = nullptr; j_cmdline = (jstring *)malloc(cmdlen * sizeof(jstring)); for (int i = 0; i < cmdlen; i++) { - jstring string = (jstring)env->GetObjectArrayElement(p_cmdline, i); const char *rawString = env->GetStringUTFChars(string, 0); @@ -739,24 +157,36 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jo } java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity())); - Engine::get_singleton()->add_singleton(Engine::Singleton("JavaClassWrapper", java_class_wrapper)); - _initialize_java_modules(); + ClassDB::register_class<JNISingleton>(); } -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, jobject p_surface, jint p_width, jint p_height) { + if (os_android) { + os_android->set_display_size(Size2i(p_width, p_height)); - if (os_android) - os_android->set_display_size(Size2(width, height)); -} + // No need to reset the surface during startup + if (step > 0) { + if (p_surface) { + ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface); + os_android->set_native_window(native_window); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jobject obj, bool p_32_bits) { + DisplayServerAndroid::get_singleton()->reset_window(); + } + } + } +} +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface, jboolean p_32_bits) { if (os_android) { if (step == 0) { // During startup os_android->set_context_is_16_bits(!p_32_bits); + if (p_surface) { + ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface); + os_android->set_native_window(native_window); + } } else { - // GL context recreated because it was lost; restart app to let it reload everything + // Rendering context recreated because it was lost; restart app to let it reload everything os_android->main_loop_end(); godot_java->restart(env); step = -1; // Ensure no further steps are attempted @@ -764,20 +194,19 @@ 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; if (step == 0) { - - // Since Godot is initialized on the UI thread, _main_thread_id was set to that thread's id, + // Since Godot is initialized on the UI thread, main_thread_id was set to that thread's id, // but for Godot purposes, the main thread is the one running the game loop Main::setup2(Thread::get_caller_id()); ++step; @@ -790,565 +219,181 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, job } os_android->main_loop_begin(); + godot_java->on_godot_main_loop_started(env); ++step; } - os_android->process_accelerometer(accelerometer); - os_android->process_gravity(gravity); - os_android->process_magnetometer(magnetometer); - os_android->process_gyroscope(gyroscope); + DisplayServerAndroid::get_singleton()->process_accelerometer(accelerometer); + DisplayServerAndroid::get_singleton()->process_gravity(gravity); + DisplayServerAndroid::get_singleton()->process_magnetometer(magnetometer); + DisplayServerAndroid::get_singleton()->process_gyroscope(gyroscope); if (os_android->main_loop_iterate()) { - godot_java->force_quit(env); } } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jobject obj, jint ev, jint pointer, jint count, jintArray positions) { - +void touch_preprocessing(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor) { if (step == 0) return; - Vector<OS_Android::TouchPos> points; - for (int i = 0; i < count; i++) { - - jint p[3]; - env->GetIntArrayRegion(positions, i * 3, 3, p); - OS_Android::TouchPos tp; + Vector<DisplayServerAndroid::TouchPos> points; + for (int i = 0; i < pointer_count; i++) { + jfloat p[3]; + env->GetFloatArrayRegion(positions, i * 3, 3, p); + DisplayServerAndroid::TouchPos tp; tp.pos = Point2(p[1], p[2]); - tp.id = p[0]; + tp.id = (int)p[0]; points.push_back(tp); } + if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE || (input_device & AINPUT_SOURCE_MOUSE_RELATIVE) == AINPUT_SOURCE_MOUSE_RELATIVE) { + DisplayServerAndroid::get_singleton()->process_mouse_event(input_device, ev, buttons_mask, points[0].pos, vertical_factor, horizontal_factor); + } else { + DisplayServerAndroid::get_singleton()->process_touch(ev, pointer, points); + } +} - os_android->process_touch(ev, pointer, points); - - /* - if (os_android) - os_android->process_touch(ev,pointer,points); - */ +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3F(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray position) { + touch_preprocessing(env, clazz, input_device, ev, pointer, pointer_count, position); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jobject obj, jint p_type, jint p_x, jint p_y) { - if (step == 0) - return; +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FI(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray position, jint buttons_mask) { + touch_preprocessing(env, clazz, input_device, ev, pointer, pointer_count, position, buttons_mask); +} - os_android->process_hover(p_type, Point2(p_x, p_y)); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FIFF(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray position, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor) { + touch_preprocessing(env, clazz, input_device, ev, pointer, pointer_count, position, buttons_mask, vertical_factor, horizontal_factor); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubletap(JNIEnv *env, jobject obj, jint p_x, jint p_y) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jfloat p_x, jfloat p_y) { if (step == 0) return; - os_android->process_double_tap(Point2(p_x, p_y)); + DisplayServerAndroid::get_singleton()->process_hover(p_type, 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_doubleTap(JNIEnv *env, jclass clazz, jint p_button_mask, jint p_x, jint p_y) { if (step == 0) return; - os_android->process_scroll(Point2(p_x, p_y)); + DisplayServerAndroid::get_singleton()->process_double_tap(p_button_mask, 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; - } - } +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jclass clazz, jint p_x, jint p_y) { + if (step == 0) + return; - return KEY_UNKNOWN; + DisplayServerAndroid::get_singleton()->process_scroll(Point2(p_x, p_y)); } -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; - OS_Android::JoypadEvent jevent; + DisplayServerAndroid::JoypadEvent jevent; jevent.device = p_device; - jevent.type = OS_Android::JOY_EVENT_BUTTON; + jevent.type = DisplayServerAndroid::JOY_EVENT_BUTTON; jevent.index = p_button; jevent.pressed = p_pressed; - os_android->process_joy_event(jevent); + DisplayServerAndroid::get_singleton()->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; - OS_Android::JoypadEvent jevent; + DisplayServerAndroid::JoypadEvent jevent; jevent.device = p_device; - jevent.type = OS_Android::JOY_EVENT_AXIS; + jevent.type = DisplayServerAndroid::JOY_EVENT_AXIS; jevent.index = p_axis; jevent.value = p_value; - os_android->process_joy_event(jevent); + DisplayServerAndroid::get_singleton()->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; - OS_Android::JoypadEvent jevent; + DisplayServerAndroid::JoypadEvent jevent; jevent.device = p_device; - jevent.type = OS_Android::JOY_EVENT_HAT; + jevent.type = DisplayServerAndroid::JOY_EVENT_HAT; int hat = 0; if (p_hat_x != 0) { if (p_hat_x < 0) - hat |= InputDefault::HAT_MASK_LEFT; + hat |= Input::HAT_MASK_LEFT; else - hat |= InputDefault::HAT_MASK_RIGHT; + hat |= Input::HAT_MASK_RIGHT; } if (p_hat_y != 0) { if (p_hat_y < 0) - hat |= InputDefault::HAT_MASK_UP; + hat |= Input::HAT_MASK_UP; else - hat |= InputDefault::HAT_MASK_DOWN; + hat |= Input::HAT_MASK_DOWN; } jevent.hat = hat; - os_android->process_joy_event(jevent); + DisplayServerAndroid::get_singleton()->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); + Input::get_singleton()->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); - ievent->set_unicode(val); - ievent->set_pressed(p_pressed); - - if (val == '\n') { - ievent->set_scancode(KEY_ENTER); - } else if (val == 61448) { - ievent->set_scancode(KEY_BACKSPACE); - ievent->set_unicode(KEY_BACKSPACE); - } else if (val == 61453) { - ievent->set_scancode(KEY_ENTER); - ievent->set_unicode(KEY_ENTER); - } else if (p_scancode == 4) { - - os_android->main_loop_request_go_back(); - } - - os_android->process_event(ievent); + DisplayServerAndroid::get_singleton()->process_key_event(p_keycode, p_scancode, p_unicode_char, p_pressed); } -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; 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; os_android->main_loop_focusout(); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jobject obj) { - - ThreadAndroid::setup_thread(); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jclass clazz) { + setup_android_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::REAL }, - { "double", Variant::REAL }, - { "java.lang.String", Variant::STRING }, - { "[I", Variant::POOL_INT_ARRAY }, - { "[B", Variant::POOL_BYTE_ARRAY }, - { "[F", Variant::POOL_REAL_ARRAY }, - { "[Ljava.lang.String;", Variant::POOL_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) { - - Object *obj = ObjectDB::get_instance(ID); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params) { + Object *obj = ObjectDB::get_instance(ObjectID(ID)); ERR_FAIL_COND(!obj); int res = env->PushLocalFrame(16); @@ -1360,7 +405,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *en Variant *vlist = (Variant *)alloca(sizeof(Variant) * count); Variant **vptr = (Variant **)alloca(sizeof(Variant *) * count); for (int i = 0; i < count; i++) { - jobject obj = env->GetObjectArrayElement(params, i); Variant v; if (obj) @@ -1371,16 +415,15 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *en env->DeleteLocalRef(obj); }; - Variant::CallError err; + Callable::CallError err; obj->call(str_method, (const Variant **)vptr, count, err); // something - env->PopLocalFrame(NULL); + env->PopLocalFrame(nullptr); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jobject p_obj, jint ID, jstring method, jobjectArray params) { - - Object *obj = ObjectDB::get_instance(ID); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params) { + Object *obj = ObjectDB::get_instance(ObjectID(ID)); ERR_FAIL_COND(!obj); int res = env->PushLocalFrame(16); @@ -1392,19 +435,19 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv * Variant args[VARIANT_ARG_MAX]; for (int i = 0; i < MIN(count, VARIANT_ARG_MAX); i++) { - jobject obj = env->GetObjectArrayElement(params, i); if (obj) args[i] = _jobject_to_variant(env, obj); env->DeleteLocalRef(obj); }; + static_assert(VARIANT_ARG_MAX == 5, "This code needs to be updated if VARIANT_ARG_MAX != 5"); obj->call_deferred(str_method, args[0], args[1], args[2], args[3], args[4]); // something - env->PopLocalFrame(NULL); + env->PopLocalFrame(nullptr); } -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(); @@ -1420,7 +463,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNI return; if (os_android->get_main_loop()) { - os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APP_RESUMED); + os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED); } } @@ -1429,6 +472,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIE return; if (os_android->get_main_loop()) { - os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APP_PAUSED); + os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED); } } +} diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index 71d4547f65..a3e2933185 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -37,36 +37,37 @@ // 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 godot_instance, jobject p_asset_manager, jboolean p_use_apk_expansion); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface, jboolean p_32_bits); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz); +void touch_preprocessing(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask = 0, jfloat vertical_factor = 0, jfloat horizontal_factor = 0); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3F(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FI(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FIFF(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jfloat p_x, jfloat p_y); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubleTap(JNIEnv *env, jclass clazz, jint p_button_mask, jint p_x, jint p_y); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jclass clazz, jint p_x, jint p_y); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_keycode, jint p_scancode, jint p_unicode_char, jboolean p_pressed); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jclass clazz, jint p_device, jint p_axis, jfloat p_value); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jclass clazz, jint p_device, jint p_hat_x, jint p_hat_y); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jclass clazz, jint p_device, jboolean p_connected, jstring p_name); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv *env, jclass clazz); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gravity(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnetometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z); +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, jlong ID, jstring method, jobjectArray params); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *env, jclass clazz, jlong 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/lib/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java b/platform/android/java_godot_view_wrapper.cpp index c78e8c1c66..5b638300ef 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java +++ b/platform/android/java_godot_view_wrapper.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ -/* CustomSSLSocketFactory.java */ +/* java_godot_view_wrapper.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,42 +28,39 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.utils; -import java.io.IOException; -import java.net.Socket; -import java.net.UnknownHostException; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; -import org.apache.http.conn.ssl.SSLSocketFactory; +#include "java_godot_view_wrapper.h" -/** - * - * @author Luis Linietsky <luis.linietsky@gmail.com> - */ -public class CustomSSLSocketFactory extends SSLSocketFactory { - SSLContext sslContext = SSLContext.getInstance("TLS"); +#include "thread_jandroid.h" - public CustomSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { - super(truststore); +GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) { + JNIEnv *env = get_jni_env(); - TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); - tmf.init(truststore); + _godot_view = env->NewGlobalRef(godot_view); - sslContext.init(null, tmf.getTrustManagers(), null); + _cls = (jclass)env->NewGlobalRef(env->GetObjectClass(godot_view)); + + if (android_get_device_api_level() >= __ANDROID_API_O__) { + _request_pointer_capture = env->GetMethodID(_cls, "requestPointerCapture", "()V"); + _release_pointer_capture = env->GetMethodID(_cls, "releasePointerCapture", "()V"); } +} - @Override - public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { - return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); +void GodotJavaViewWrapper::request_pointer_capture() { + if (_request_pointer_capture != 0) { + JNIEnv *env = get_jni_env(); + env->CallVoidMethod(_godot_view, _request_pointer_capture); } +} - @Override - public Socket createSocket() throws IOException { - return sslContext.getSocketFactory().createSocket(); +void GodotJavaViewWrapper::release_pointer_capture() { + if (_request_pointer_capture != 0) { + JNIEnv *env = get_jni_env(); + env->CallVoidMethod(_godot_view, _release_pointer_capture); } } + +GodotJavaViewWrapper::~GodotJavaViewWrapper() { + JNIEnv *env = get_jni_env(); + env->DeleteGlobalRef(_godot_view); + env->DeleteGlobalRef(_cls); +} diff --git a/platform/android/java_godot_view_wrapper.h b/platform/android/java_godot_view_wrapper.h new file mode 100644 index 0000000000..548c278292 --- /dev/null +++ b/platform/android/java_godot_view_wrapper.h @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* java_godot_view_wrapper.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GODOT_JAVA_GODOT_VIEW_WRAPPER_H +#define GODOT_JAVA_GODOT_VIEW_WRAPPER_H + +#include <android/log.h> +#include <jni.h> + +// Class that makes functions in java/src/org/godotengine/godot/GodotView.java callable from C++ +class GodotJavaViewWrapper { +private: + jclass _cls; + + jobject _godot_view; + + jmethodID _request_pointer_capture = 0; + jmethodID _release_pointer_capture = 0; + +public: + GodotJavaViewWrapper(jobject godot_view); + + void request_pointer_capture(); + void release_pointer_capture(); + + ~GodotJavaViewWrapper(); +}; + +#endif //GODOT_JAVA_GODOT_VIEW_WRAPPER_H diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 893b786c0b..759980373a 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -33,39 +33,51 @@ // JNIEnv is only valid within the thread it belongs to, in a multi threading environment // we can't cache it. // For Godot we call most access methods from our thread and we thus get a valid JNIEnv -// from ThreadAndroid. For one or two we expect to pass the environment +// from get_jni_env(). For one or two we expect to pass the environment // TODO we could probably create a base class for this... -GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) { +GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance) { godot_instance = p_env->NewGlobalRef(p_godot_instance); + activity = p_env->NewGlobalRef(p_activity); // get info about our Godot class so we can get pointers and stuff... - cls = p_env->FindClass("org/godotengine/godot/Godot"); - if (cls) { - cls = (jclass)p_env->NewGlobalRef(cls); + godot_class = p_env->FindClass("org/godotengine/godot/Godot"); + if (godot_class) { + godot_class = (jclass)p_env->NewGlobalRef(godot_class); + } else { + // this is a pretty serious fail.. bail... pointers will stay 0 + return; + } + activity_class = p_env->FindClass("android/app/Activity"); + if (activity_class) { + activity_class = (jclass)p_env->NewGlobalRef(activity_class); } else { // this is a pretty serious fail.. bail... pointers will stay 0 return; } - // get some method pointers... - _on_video_init = p_env->GetMethodID(cls, "onVideoInit", "()V"); - _restart = p_env->GetMethodID(cls, "restart", "()V"); - _finish = p_env->GetMethodID(cls, "forceQuit", "()V"); - _set_keep_screen_on = p_env->GetMethodID(cls, "setKeepScreenOn", "(Z)V"); - _alert = p_env->GetMethodID(cls, "alert", "(Ljava/lang/String;Ljava/lang/String;)V"); - _get_GLES_version_code = p_env->GetMethodID(cls, "getGLESVersionCode", "()I"); - _get_clipboard = p_env->GetMethodID(cls, "getClipboard", "()Ljava/lang/String;"); - _set_clipboard = p_env->GetMethodID(cls, "setClipboard", "(Ljava/lang/String;)V"); - _request_permission = p_env->GetMethodID(cls, "requestPermission", "(Ljava/lang/String;)Z"); - _request_permissions = p_env->GetMethodID(cls, "requestPermissions", "()Z"); - _get_granted_permissions = p_env->GetMethodID(cls, "getGrantedPermissions", "()[Ljava/lang/String;"); - _init_input_devices = p_env->GetMethodID(cls, "initInputDevices", "()V"); - _get_surface = p_env->GetMethodID(cls, "getSurface", "()Landroid/view/Surface;"); - _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;"); + // get some Godot method pointers... + _on_video_init = p_env->GetMethodID(godot_class, "onVideoInit", "()V"); + _restart = p_env->GetMethodID(godot_class, "restart", "()V"); + _finish = p_env->GetMethodID(godot_class, "forceQuit", "()V"); + _set_keep_screen_on = p_env->GetMethodID(godot_class, "setKeepScreenOn", "(Z)V"); + _alert = p_env->GetMethodID(godot_class, "alert", "(Ljava/lang/String;Ljava/lang/String;)V"); + _get_GLES_version_code = p_env->GetMethodID(godot_class, "getGLESVersionCode", "()I"); + _get_clipboard = p_env->GetMethodID(godot_class, "getClipboard", "()Ljava/lang/String;"); + _set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V"); + _request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z"); + _request_permissions = p_env->GetMethodID(godot_class, "requestPermissions", "()Z"); + _get_granted_permissions = p_env->GetMethodID(godot_class, "getGrantedPermissions", "()[Ljava/lang/String;"); + _init_input_devices = p_env->GetMethodID(godot_class, "initInputDevices", "()V"); + _get_surface = p_env->GetMethodID(godot_class, "getSurface", "()Landroid/view/Surface;"); + _is_activity_resumed = p_env->GetMethodID(godot_class, "isActivityResumed", "()Z"); + _vibrate = p_env->GetMethodID(godot_class, "vibrate", "(I)V"); + _get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;"); + _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V"); + + // get some Activity method pointers... + _get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -73,73 +85,83 @@ GodotJavaWrapper::~GodotJavaWrapper() { } jobject GodotJavaWrapper::get_activity() { - // our godot instance is our activity - return godot_instance; + return activity; } jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env) { - if (cls) { - if (p_env == NULL) - p_env = ThreadAndroid::get_env(); + if (godot_class) { + if (p_env == nullptr) + p_env = get_jni_env(); - jfieldID fid = p_env->GetStaticFieldID(cls, p_name, p_class); - return p_env->GetStaticObjectField(cls, fid); + jfieldID fid = p_env->GetStaticFieldID(godot_class, p_name, p_class); + return p_env->GetStaticObjectField(godot_class, fid); } else { - return NULL; + return nullptr; } } jobject GodotJavaWrapper::get_class_loader() { - if (cls) { - JNIEnv *env = ThreadAndroid::get_env(); - jmethodID getClassLoader = env->GetMethodID(cls, "getClassLoader", "()Ljava/lang/ClassLoader;"); - return env->CallObjectMethod(godot_instance, getClassLoader); + if (_get_class_loader) { + JNIEnv *env = get_jni_env(); + return env->CallObjectMethod(activity, _get_class_loader); } else { - return NULL; + return nullptr; } } -void GodotJavaWrapper::gfx_init(bool gl2) { - // beats me what this once did, there was no code, - // but we're getting false if our GLES3 driver is initialised - // and true for our GLES2 driver - // Maybe we're supposed to communicate this back or store it? +GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() { + if (_godot_view != nullptr) { + return _godot_view; + } + JNIEnv *env = get_jni_env(); + jmethodID godot_view_getter = env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;"); + _godot_view = new GodotJavaViewWrapper(env->CallObjectMethod(godot_instance, godot_view_getter)); + return _godot_view; } void GodotJavaWrapper::on_video_init(JNIEnv *p_env) { if (_on_video_init) - if (p_env == NULL) - p_env = ThreadAndroid::get_env(); + if (p_env == nullptr) + p_env = get_jni_env(); p_env->CallVoidMethod(godot_instance, _on_video_init); } +void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) { + if (_on_godot_main_loop_started) { + if (p_env == nullptr) { + p_env = get_jni_env(); + } + } + p_env->CallVoidMethod(godot_instance, _on_godot_main_loop_started); +} + void GodotJavaWrapper::restart(JNIEnv *p_env) { if (_restart) - if (p_env == NULL) - p_env = ThreadAndroid::get_env(); + if (p_env == nullptr) + p_env = get_jni_env(); p_env->CallVoidMethod(godot_instance, _restart); } void GodotJavaWrapper::force_quit(JNIEnv *p_env) { if (_finish) - if (p_env == NULL) - p_env = ThreadAndroid::get_env(); + if (p_env == nullptr) + p_env = get_jni_env(); p_env->CallVoidMethod(godot_instance, _finish); } void GodotJavaWrapper::set_keep_screen_on(bool p_enabled) { if (_set_keep_screen_on) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(godot_instance, _set_keep_screen_on, p_enabled); } } void GodotJavaWrapper::alert(const String &p_message, const String &p_title) { if (_alert) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring jStrMessage = env->NewStringUTF(p_message.utf8().get_data()); jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data()); env->CallVoidMethod(godot_instance, _alert, jStrMessage, jStrTitle); @@ -147,7 +169,7 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) { } int GodotJavaWrapper::get_gles_version_code() { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); if (_get_GLES_version_code) { return env->CallIntMethod(godot_instance, _get_GLES_version_code); } @@ -161,7 +183,7 @@ bool GodotJavaWrapper::has_get_clipboard() { String GodotJavaWrapper::get_clipboard() { if (_get_clipboard) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring s = (jstring)env->CallObjectMethod(godot_instance, _get_clipboard); return jstring_to_string(s, env); } else { @@ -171,7 +193,7 @@ String GodotJavaWrapper::get_clipboard() { String GodotJavaWrapper::get_input_fallback_mapping() { if (_get_input_fallback_mapping) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring fallback_mapping = (jstring)env->CallObjectMethod(godot_instance, _get_input_fallback_mapping); return jstring_to_string(fallback_mapping, env); } else { @@ -185,7 +207,7 @@ bool GodotJavaWrapper::has_set_clipboard() { void GodotJavaWrapper::set_clipboard(const String &p_text) { if (_set_clipboard) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring jStr = env->NewStringUTF(p_text.utf8().get_data()); env->CallVoidMethod(godot_instance, _set_clipboard, jStr); } @@ -193,7 +215,7 @@ void GodotJavaWrapper::set_clipboard(const String &p_text) { bool GodotJavaWrapper::request_permission(const String &p_name) { if (_request_permission) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jstring jStrName = env->NewStringUTF(p_name.utf8().get_data()); return env->CallBooleanMethod(godot_instance, _request_permission, jStrName); } else { @@ -203,7 +225,7 @@ bool GodotJavaWrapper::request_permission(const String &p_name) { bool GodotJavaWrapper::request_permissions() { if (_request_permissions) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); return env->CallBooleanMethod(godot_instance, _request_permissions); } else { return false; @@ -213,7 +235,7 @@ bool GodotJavaWrapper::request_permissions() { Vector<String> GodotJavaWrapper::get_granted_permissions() const { Vector<String> permissions_list; if (_get_granted_permissions) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); jobject permissions_object = env->CallObjectMethod(godot_instance, _get_granted_permissions); jobjectArray *arr = reinterpret_cast<jobjectArray *>(&permissions_object); @@ -231,23 +253,23 @@ Vector<String> GodotJavaWrapper::get_granted_permissions() const { void GodotJavaWrapper::init_input_devices() { if (_init_input_devices) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(godot_instance, _init_input_devices); } } jobject GodotJavaWrapper::get_surface() { if (_get_surface) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); return env->CallObjectMethod(godot_instance, _get_surface); } else { - return NULL; + return nullptr; } } bool GodotJavaWrapper::is_activity_resumed() { if (_is_activity_resumed) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); return env->CallBooleanMethod(godot_instance, _is_activity_resumed); } else { return false; @@ -256,7 +278,7 @@ bool GodotJavaWrapper::is_activity_resumed() { void GodotJavaWrapper::vibrate(int p_duration_ms) { if (_vibrate) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(godot_instance, _vibrate, p_duration_ms); } } diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 655f5170bf..99a60dffa1 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -37,13 +37,18 @@ #include <android/log.h> #include <jni.h> +#include "java_godot_view_wrapper.h" #include "string_android.h" // Class that makes functions in java/src/org/godotengine/godot/Godot.java callable from C++ class GodotJavaWrapper { private: jobject godot_instance; - jclass cls; + jobject activity; + jclass godot_class; + jclass activity_class; + + GodotJavaViewWrapper *_godot_view = nullptr; jmethodID _on_video_init = 0; jmethodID _restart = 0; @@ -61,20 +66,23 @@ private: jmethodID _is_activity_resumed = 0; jmethodID _vibrate = 0; jmethodID _get_input_fallback_mapping = 0; + jmethodID _on_godot_main_loop_started = 0; + jmethodID _get_class_loader = 0; public: - GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance); + GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance); ~GodotJavaWrapper(); jobject get_activity(); - jobject get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env = NULL); + jobject get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env = nullptr); jobject get_class_loader(); + GodotJavaViewWrapper *get_godot_view(); - void gfx_init(bool gl2); - void on_video_init(JNIEnv *p_env = NULL); - void restart(JNIEnv *p_env = NULL); - void force_quit(JNIEnv *p_env = NULL); + void on_video_init(JNIEnv *p_env = nullptr); + void on_godot_main_loop_started(JNIEnv *p_env = nullptr); + void restart(JNIEnv *p_env = nullptr); + void force_quit(JNIEnv *p_env = nullptr); void set_keep_screen_on(bool p_enabled); void alert(const String &p_message, const String &p_title); int get_gles_version_code(); diff --git a/platform/android/jni_utils.cpp b/platform/android/jni_utils.cpp new file mode 100644 index 0000000000..94652ab7ac --- /dev/null +++ b/platform/android/jni_utils.cpp @@ -0,0 +1,400 @@ +/*************************************************************************/ +/* jni_utils.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "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"), nullptr); + + 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 == nullptr) { + 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 }, + { nullptr, 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;" }, + { nullptr, "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..f2b89392b5 --- /dev/null +++ b/platform/android/jni_utils.h @@ -0,0 +1,55 @@ +/*************************************************************************/ +/* jni_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef JNI_UTILS_H +#define JNI_UTILS_H + +#include "string_android.h" +#include <core/config/engine.h> +#include <core/variant/variant.h> +#include <jni.h> + +struct jvalret { + jobject obj; + jvalue val; + jvalret() { obj = nullptr; } +}; + +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); + +#endif // JNI_UTILS_H diff --git a/platform/android/logo.png b/platform/android/logo.png Binary files differindex df445f6a9c..f44d360a25 100644 --- a/platform/android/logo.png +++ b/platform/android/logo.png diff --git a/platform/android/net_socket_android.cpp b/platform/android/net_socket_android.cpp index 320bdd3817..ddc2368793 100644 --- a/platform/android/net_socket_android.cpp +++ b/platform/android/net_socket_android.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -38,8 +38,7 @@ jmethodID NetSocketAndroid::_multicast_lock_acquire = 0; jmethodID NetSocketAndroid::_multicast_lock_release = 0; void NetSocketAndroid::setup(jobject p_net_utils) { - - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); net_utils = env->NewGlobalRef(p_net_utils); @@ -52,14 +51,14 @@ void NetSocketAndroid::setup(jobject p_net_utils) { void NetSocketAndroid::multicast_lock_acquire() { if (_multicast_lock_acquire) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(net_utils, _multicast_lock_acquire); } } void NetSocketAndroid::multicast_lock_release() { if (_multicast_lock_release) { - JNIEnv *env = ThreadAndroid::get_env(); + JNIEnv *env = get_jni_env(); env->CallVoidMethod(net_utils, _multicast_lock_release); } } @@ -72,11 +71,6 @@ void NetSocketAndroid::make_default() { _create = _create_func; } -NetSocketAndroid::NetSocketAndroid() : - wants_broadcast(false), - multicast_groups(0) { -} - NetSocketAndroid::~NetSocketAndroid() { close(); } diff --git a/platform/android/net_socket_android.h b/platform/android/net_socket_android.h index 4fc80d2de1..cc2a68ac49 100644 --- a/platform/android/net_socket_android.h +++ b/platform/android/net_socket_android.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -45,15 +45,14 @@ * joins/leaves a multicast group. */ class NetSocketAndroid : public NetSocketPosix { - private: static jobject net_utils; static jclass cls; static jmethodID _multicast_lock_acquire; static jmethodID _multicast_lock_release; - bool wants_broadcast; - int multicast_groups; + bool wants_broadcast = false; + int multicast_groups = 0; static void multicast_lock_acquire(); static void multicast_lock_release(); @@ -71,7 +70,7 @@ public: virtual Error join_multicast_group(const IP_Address &p_multi_address, String p_if_name); virtual Error leave_multicast_group(const IP_Address &p_multi_address, String p_if_name); - NetSocketAndroid(); + NetSocketAndroid() {} ~NetSocketAndroid(); }; diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index bbea5e3699..422814dd50 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,19 +30,14 @@ #include "os_android.h" -#include "core/io/file_access_buffered_fa.h" -#include "core/project_settings.h" -#include "drivers/gles2/rasterizer_gles2.h" -#include "drivers/gles3/rasterizer_gles3.h" +#include "core/config/project_settings.h" #include "drivers/unix/dir_access_unix.h" #include "drivers/unix/file_access_unix.h" -#include "file_access_android.h" #include "main/main.h" -#include "servers/visual/visual_server_raster.h" -#include "servers/visual/visual_server_wrap_mt.h" +#include "platform/android/display_server_android.h" #include "dir_access_jandroid.h" -#include "file_access_jandroid.h" +#include "file_access_android.h" #include "net_socket_android.h" #include <dlfcn.h> @@ -59,48 +54,16 @@ public: virtual ~AndroidLogger() {} }; -int OS_Android::get_video_driver_count() const { - - return 2; -} - -const char *OS_Android::get_video_driver_name(int p_driver) const { - - switch (p_driver) { - case VIDEO_DRIVER_GLES3: - return "GLES3"; - case VIDEO_DRIVER_GLES2: - return "GLES2"; - } - ERR_FAIL_V_MSG(NULL, "Invalid video driver index: " + itos(p_driver) + "."); -} -int OS_Android::get_audio_driver_count() const { - - return 1; -} - -const char *OS_Android::get_audio_driver_name(int p_driver) const { - - return "Android"; -} - void OS_Android::initialize_core() { - OS_Unix::initialize_core(); if (use_apk_expansion) FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES); else { -#ifdef USE_JAVA_FILE_ACCESS - FileAccess::make_default<FileAccessBufferedFA<FileAccessJAndroid> >(FileAccess::ACCESS_RESOURCES); -#else - //FileAccess::make_default<FileAccessBufferedFA<FileAccessAndroid> >(FileAccess::ACCESS_RESOURCES); FileAccess::make_default<FileAccessAndroid>(FileAccess::ACCESS_RESOURCES); -#endif } FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA); FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM); - //FileAccessBufferedFA<FileAccessUnix>::make_default(); if (use_apk_expansion) DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES); else @@ -111,92 +74,33 @@ void OS_Android::initialize_core() { NetSocketAndroid::make_default(); } -void OS_Android::set_opengl_extensions(const char *p_gl_extensions) { - - ERR_FAIL_COND(!p_gl_extensions); - gl_extensions = p_gl_extensions; +void OS_Android::initialize() { + initialize_core(); } -int OS_Android::get_current_video_driver() const { - return video_driver_index; -} - -Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - - bool use_gl3 = godot_java->get_gles_version_code() >= 0x00030000; - use_gl3 = use_gl3 && (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES3"); - bool gl_initialization_error = false; - - while (true) { - if (use_gl3) { - if (RasterizerGLES3::is_viable() == OK) { - godot_java->gfx_init(false); - RasterizerGLES3::register_config(); - RasterizerGLES3::make_current(); - break; - } else { - if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) { - p_video_driver = VIDEO_DRIVER_GLES2; - use_gl3 = false; - continue; - } else { - gl_initialization_error = true; - break; - } - } - } else { - if (RasterizerGLES2::is_viable() == OK) { - godot_java->gfx_init(true); - RasterizerGLES2::register_config(); - RasterizerGLES2::make_current(); - break; - } else { - gl_initialization_error = true; - break; - } - } - } - - 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; - } - - video_driver_index = p_video_driver; - - visual_server = memnew(VisualServerRaster); - if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { - visual_server = memnew(VisualServerWrapMT(visual_server, false)); - } - - visual_server->init(); - - AudioDriverManager::initialize(p_audio_driver); +void OS_Android::initialize_joypads() { + Input::get_singleton()->set_fallback_mapping(godot_java->get_input_fallback_mapping()); - input = memnew(InputDefault); - input->set_fallback_mapping(godot_java->get_input_fallback_mapping()); - - //power_manager = memnew(PowerAndroid); - - return OK; + // This queries/updates the currently connected devices/joypads. + godot_java->init_input_devices(); } void OS_Android::set_main_loop(MainLoop *p_main_loop) { - main_loop = p_main_loop; - input->set_main_loop(p_main_loop); } void OS_Android::delete_main_loop() { - - memdelete(main_loop); + if (main_loop) { + memdelete(main_loop); + main_loop = nullptr; + } } void OS_Android::finalize() { +} - memdelete(input); +OS_Android *OS_Android::get_singleton() { + return (OS_Android *)OS::get_singleton(); } GodotJavaWrapper *OS_Android::get_godot_java() { @@ -207,24 +111,15 @@ GodotIOJavaWrapper *OS_Android::get_godot_io_java() { return godot_io_java; } -void OS_Android::alert(const String &p_alert, const String &p_title) { - - //print("ALERT: %s\n", p_alert.utf8().get_data()); - godot_java->alert(p_alert, p_title); -} - bool OS_Android::request_permission(const String &p_name) { - return godot_java->request_permission(p_name); } bool OS_Android::request_permissions() { - return godot_java->request_permissions(); } Vector<String> OS_Android::get_granted_permissions() const { - return godot_java->get_granted_permissions(); } @@ -234,384 +129,54 @@ Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_han return OK; } -void OS_Android::set_mouse_show(bool p_show) { - - //android has no mouse... -} - -void OS_Android::set_mouse_grab(bool p_grab) { - - //it really has no mouse...! -} - -bool OS_Android::is_mouse_grab_enabled() const { - - //*sigh* technology has evolved so much since i was a kid.. - return false; -} - -Point2 OS_Android::get_mouse_position() const { - - return Point2(); -} - -int OS_Android::get_mouse_button_state() const { - - return 0; -} - -void OS_Android::set_window_title(const String &p_title) { - //This queries/updates the currently connected devices/joypads - //Set_window_title is called when initializing the main loop (main.cpp) - //therefore this place is found to be suitable (I found no better). - godot_java->init_input_devices(); -} - -void OS_Android::set_video_mode(const VideoMode &p_video_mode, int p_screen) { -} - -OS::VideoMode OS_Android::get_video_mode(int p_screen) const { - - return default_videomode; -} - -void OS_Android::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { - - p_list->push_back(default_videomode); -} - -void OS_Android::set_keep_screen_on(bool p_enabled) { - OS::set_keep_screen_on(p_enabled); - - godot_java->set_keep_screen_on(p_enabled); -} - -Size2 OS_Android::get_window_size() const { - - return Vector2(default_videomode.width, default_videomode.height); -} - String OS_Android::get_name() const { - return "Android"; } MainLoop *OS_Android::get_main_loop() const { - return main_loop; } -bool OS_Android::can_draw() const { - - return true; //always? -} - void OS_Android::main_loop_begin() { - if (main_loop) - main_loop->init(); + main_loop->initialize(); } bool OS_Android::main_loop_iterate() { - if (!main_loop) return false; + DisplayServerAndroid::get_singleton()->process_events(); return Main::iteration(); } void OS_Android::main_loop_end() { - if (main_loop) - main_loop->finish(); + main_loop->finalize(); } void OS_Android::main_loop_focusout() { - - if (main_loop) - main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT); + DisplayServerAndroid::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT); audio_driver_android.set_pause(true); } void OS_Android::main_loop_focusin() { - - if (main_loop) - main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN); + DisplayServerAndroid::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN); audio_driver_android.set_pause(false); } -void OS_Android::process_joy_event(OS_Android::JoypadEvent p_event) { - - switch (p_event.type) { - case JOY_EVENT_BUTTON: - input->joy_button(p_event.device, p_event.index, p_event.pressed); - break; - case JOY_EVENT_AXIS: - InputDefault::JoyAxis value; - value.min = -1; - value.value = p_event.value; - input->joy_axis(p_event.device, p_event.index, value); - break; - case JOY_EVENT_HAT: - input->joy_hat(p_event.device, p_event.hat); - break; - default: - return; - } -} - -void OS_Android::process_event(Ref<InputEvent> p_event) { - - input->parse_input_event(p_event); -} - -void OS_Android::process_touch(int p_what, int p_pointer, const Vector<TouchPos> &p_points) { - - switch (p_what) { - case 0: { //gesture begin - - if (touch.size()) { - //end all if exist - for (int i = 0; i < touch.size(); i++) { - - Ref<InputEventScreenTouch> ev; - ev.instance(); - ev->set_index(touch[i].id); - ev->set_pressed(false); - ev->set_position(touch[i].pos); - input->parse_input_event(ev); - } - } - - touch.resize(p_points.size()); - for (int i = 0; i < p_points.size(); i++) { - touch.write[i].id = p_points[i].id; - touch.write[i].pos = p_points[i].pos; - } - - //send touch - for (int i = 0; i < touch.size(); i++) { - - Ref<InputEventScreenTouch> ev; - ev.instance(); - ev->set_index(touch[i].id); - ev->set_pressed(true); - ev->set_position(touch[i].pos); - input->parse_input_event(ev); - } - - } break; - case 1: { //motion - - ERR_FAIL_COND(touch.size() != p_points.size()); - - for (int i = 0; i < touch.size(); i++) { - - int idx = -1; - for (int j = 0; j < p_points.size(); j++) { - - if (touch[i].id == p_points[j].id) { - idx = j; - break; - } - } - - ERR_CONTINUE(idx == -1); - - if (touch[i].pos == p_points[idx].pos) - continue; //no move unncesearily - - Ref<InputEventScreenDrag> ev; - ev.instance(); - ev->set_index(touch[i].id); - ev->set_position(p_points[idx].pos); - ev->set_relative(p_points[idx].pos - touch[i].pos); - input->parse_input_event(ev); - touch.write[i].pos = p_points[idx].pos; - } - - } break; - case 2: { //release - - if (touch.size()) { - //end all if exist - for (int i = 0; i < touch.size(); i++) { - - Ref<InputEventScreenTouch> ev; - ev.instance(); - ev->set_index(touch[i].id); - ev->set_pressed(false); - ev->set_position(touch[i].pos); - input->parse_input_event(ev); - } - touch.clear(); - } - } break; - case 3: { // add touch - - for (int i = 0; i < p_points.size(); i++) { - if (p_points[i].id == p_pointer) { - TouchPos tp = p_points[i]; - touch.push_back(tp); - - Ref<InputEventScreenTouch> ev; - ev.instance(); - - ev->set_index(tp.id); - ev->set_pressed(true); - ev->set_position(tp.pos); - input->parse_input_event(ev); - - break; - } - } - } break; - case 4: { // remove touch - - for (int i = 0; i < touch.size(); i++) { - if (touch[i].id == p_pointer) { - - Ref<InputEventScreenTouch> ev; - ev.instance(); - ev->set_index(touch[i].id); - ev->set_pressed(false); - ev->set_position(touch[i].pos); - input->parse_input_event(ev); - touch.remove(i); - - break; - } - } - } break; - } -} - -void OS_Android::process_hover(int p_type, Point2 p_pos) { - // https://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER - switch (p_type) { - case 7: // hover move - case 9: // hover enter - case 10: { // hover exit - Ref<InputEventMouseMotion> ev; - ev.instance(); - ev->set_position(p_pos); - ev->set_global_position(p_pos); - ev->set_relative(p_pos - hover_prev_pos); - input->parse_input_event(ev); - hover_prev_pos = p_pos; - } break; - } -} - -void OS_Android::process_double_tap(Point2 p_pos) { - Ref<InputEventMouseButton> ev; - ev.instance(); - ev->set_position(p_pos); - ev->set_global_position(p_pos); - ev->set_pressed(true); - ev->set_doubleclick(true); - ev->set_button_index(1); - input->parse_input_event(ev); -} - -void OS_Android::process_scroll(Point2 p_pos) { - Ref<InputEventPanGesture> ev; - ev.instance(); - ev->set_position(p_pos); - ev->set_delta(p_pos - scroll_prev_pos); - input->parse_input_event(ev); - scroll_prev_pos = p_pos; -} - -void OS_Android::process_accelerometer(const Vector3 &p_accelerometer) { - - input->set_accelerometer(p_accelerometer); -} - -void OS_Android::process_gravity(const Vector3 &p_gravity) { - - input->set_gravity(p_gravity); -} - -void OS_Android::process_magnetometer(const Vector3 &p_magnetometer) { - - input->set_magnetometer(p_magnetometer); -} - -void OS_Android::process_gyroscope(const Vector3 &p_gyroscope) { - - input->set_gyroscope(p_gyroscope); -} - -bool OS_Android::has_touchscreen_ui_hint() const { - - return true; -} - -bool OS_Android::has_virtual_keyboard() const { - - return true; -} - -int OS_Android::get_virtual_keyboard_height() const { - return godot_io_java->get_vk_height(); - - // ERR_PRINT("Cannot obtain virtual keyboard height."); - // return 0; -} - -void OS_Android::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect) { - - if (godot_io_java->has_vk()) { - godot_io_java->show_vk(p_existing_text); - } else { - - ERR_PRINT("Virtual keyboard not available"); - }; -} - -void OS_Android::hide_virtual_keyboard() { - - if (godot_io_java->has_vk()) { - - godot_io_java->hide_vk(); - } else { - - ERR_PRINT("Virtual keyboard not available"); - }; -} - -void OS_Android::init_video_mode(int p_video_width, int p_video_height) { - - default_videomode.width = p_video_width; - default_videomode.height = p_video_height; - default_videomode.fullscreen = true; - default_videomode.resizable = false; -} - void OS_Android::main_loop_request_go_back() { - - if (main_loop) - main_loop->notification(MainLoop::NOTIFICATION_WM_GO_BACK_REQUEST); -} - -void OS_Android::set_display_size(Size2 p_size) { - - default_videomode.width = p_size.x; - default_videomode.height = p_size.y; + DisplayServerAndroid::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_GO_BACK_REQUEST); } Error OS_Android::shell_open(String p_uri) { - return godot_io_java->open_uri(p_uri); } String OS_Android::get_resource_dir() const { - return "/"; //android has its own filesystem for resources inside the APK } String OS_Android::get_locale() const { - String locale = godot_io_java->get_locale(); if (locale != "") { return locale; @@ -620,28 +185,7 @@ String OS_Android::get_locale() const { return OS_Unix::get_locale(); } -void OS_Android::set_clipboard(const String &p_text) { - - // DO we really need the fallback to OS_Unix here?! - if (godot_java->has_set_clipboard()) { - godot_java->set_clipboard(p_text); - } else { - OS_Unix::set_clipboard(p_text); - } -} - -String OS_Android::get_clipboard() const { - - // DO we really need the fallback to OS_Unix here?! - if (godot_java->has_get_clipboard()) { - return godot_java->get_clipboard(); - } - - return OS_Unix::get_clipboard(); -} - String OS_Android::get_model_name() const { - String model = godot_io_java->get_model(); if (model != "") return model; @@ -649,19 +193,12 @@ String OS_Android::get_model_name() const { return OS_Unix::get_model_name(); } -int OS_Android::get_screen_dpi(int p_screen) const { - - return godot_io_java->get_screen_dpi(); -} - String OS_Android::get_user_data_dir() const { - if (data_dir_cache != String()) return data_dir_cache; String data_dir = godot_io_java->get_user_data_dir(); if (data_dir != "") { - //store current dir char real_current_dir_name[2048]; getcwd(real_current_dir_name, 2048); @@ -685,13 +222,7 @@ String OS_Android::get_user_data_dir() const { return "."; } -void OS_Android::set_screen_orientation(ScreenOrientation p_orientation) { - - godot_io_java->set_screen_orientation(p_orientation); -} - String OS_Android::get_unique_id() const { - String unique_id = godot_io_java->get_unique_id(); if (unique_id != "") return unique_id; @@ -699,50 +230,45 @@ String OS_Android::get_unique_id() const { return OS::get_unique_id(); } -Error OS_Android::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) { - // FIXME: Add support for volume, audio and subtitle tracks - - godot_io_java->play_video(p_path); - return OK; -} - -bool OS_Android::native_video_is_playing() const { - - return godot_io_java->is_video_playing(); -} - -void OS_Android::native_video_pause() { - - godot_io_java->pause_video(); -} - String OS_Android::get_system_dir(SystemDir p_dir) const { - return godot_io_java->get_system_dir(p_dir); } -void OS_Android::native_video_stop() { +void OS_Android::set_display_size(const Size2i &p_size) { + display_size = p_size; +} - godot_io_java->stop_video(); +Size2i OS_Android::get_display_size() const { + return display_size; } void OS_Android::set_context_is_16_bits(bool p_is_16) { - +#if defined(OPENGL_ENABLED) //use_16bits_fbo = p_is_16; //if (rasterizer) // rasterizer->set_force_16_bits_fbo(p_is_16); +#endif } -void OS_Android::joy_connection_changed(int p_device, bool p_connected, String p_name) { - return input->joy_connection_changed(p_device, p_connected, p_name, ""); +void OS_Android::set_opengl_extensions(const char *p_gl_extensions) { +#if defined(OPENGL_ENABLED) + ERR_FAIL_COND(!p_gl_extensions); + gl_extensions = p_gl_extensions; +#endif } -bool OS_Android::is_joy_known(int p_device) { - return input->is_joy_mapped(p_device); +void OS_Android::set_native_window(ANativeWindow *p_native_window) { +#if defined(VULKAN_ENABLED) + native_window = p_native_window; +#endif } -String OS_Android::get_joy_guid(int p_device) const { - return input->get_joy_guid_remapped(p_device); +ANativeWindow *OS_Android::get_native_window() const { +#if defined(VULKAN_ENABLED) + return native_window; +#else + return nullptr; +#endif } void OS_Android::vibrate_handheld(int p_duration_ms) { @@ -751,7 +277,6 @@ void OS_Android::vibrate_handheld(int p_duration_ms) { bool OS_Android::_check_internal_feature_support(const String &p_feature) { if (p_feature == "mobile") { - //TODO support etc2 only if GLES3 driver is selected return true; } #if defined(__aarch64__) @@ -771,17 +296,22 @@ bool OS_Android::_check_internal_feature_support(const String &p_feature) { } OS_Android::OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion) { + display_size.width = 800; + display_size.height = 600; use_apk_expansion = p_use_apk_expansion; - default_videomode.width = 800; - default_videomode.height = 600; - default_videomode.fullscreen = true; - default_videomode.resizable = false; - - main_loop = NULL; - gl_extensions = NULL; - //rasterizer = NULL; + + main_loop = nullptr; + +#if defined(OPENGL_ENABLED) + gl_extensions = nullptr; use_gl2 = false; + use_16bits_fbo = false; +#endif + +#if defined(VULKAN_ENABLED) + native_window = nullptr; +#endif godot_java = p_godot_java; godot_io_java = p_godot_io_java; @@ -791,6 +321,8 @@ OS_Android::OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_god _set_logger(memnew(CompositeLogger(loggers))); AudioDriverManager::add_driver(&audio_driver_android); + + DisplayServerAndroid::register_android_driver(); } OS_Android::~OS_Android() { diff --git a/platform/android/os_android.h b/platform/android/os_android.h index 1cf64a2e84..dd14b69cf9 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -33,120 +33,65 @@ #include "audio_driver_jandroid.h" #include "audio_driver_opensl.h" -#include "core/os/input.h" #include "core/os/main_loop.h" #include "drivers/unix/os_unix.h" -#include "main/input_default.h" -//#include "power_android.h" #include "servers/audio_server.h" -#include "servers/visual/rasterizer.h" class GodotJavaWrapper; class GodotIOJavaWrapper; -class OS_Android : public OS_Unix { -public: - struct TouchPos { - int id; - Point2 pos; - }; - - enum { - JOY_EVENT_BUTTON = 0, - JOY_EVENT_AXIS = 1, - JOY_EVENT_HAT = 2 - }; - - struct JoypadEvent { - - int device; - int type; - int index; - bool pressed; - float value; - int hat; - }; +struct ANativeWindow; +class OS_Android : public OS_Unix { private: - Vector<TouchPos> touch; - Point2 hover_prev_pos; // needed to calculate the relative position on hover events - Point2 scroll_prev_pos; // needed to calculate the relative position on scroll events + Size2i display_size; - bool use_gl2; bool use_apk_expansion; +#if defined(OPENGL_ENABLED) bool use_16bits_fbo; + const char *gl_extensions; +#endif - VisualServer *visual_server; +#if defined(VULKAN_ENABLED) + ANativeWindow *native_window; +#endif mutable String data_dir_cache; //AudioDriverAndroid audio_driver_android; AudioDriverOpenSL audio_driver_android; - const char *gl_extensions; - - InputDefault *input; - VideoMode default_videomode; MainLoop *main_loop; GodotJavaWrapper *godot_java; GodotIOJavaWrapper *godot_io_java; - //PowerAndroid *power_manager_func; - - int video_driver_index; - public: - // functions used by main to initialize/deinitialize the OS - virtual int get_video_driver_count() const; - virtual const char *get_video_driver_name(int p_driver) const; - - virtual int get_audio_driver_count() const; - virtual const char *get_audio_driver_name(int p_driver) const; - - virtual int get_current_video_driver() const; + virtual void initialize_core() override; + virtual void initialize() override; - virtual void initialize_core(); - virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); + virtual void initialize_joypads() override; - virtual void set_main_loop(MainLoop *p_main_loop); - virtual void delete_main_loop(); + virtual void set_main_loop(MainLoop *p_main_loop) override; + virtual void delete_main_loop() override; - virtual void finalize(); + virtual void finalize() override; typedef int64_t ProcessID; - static OS *get_singleton(); + static OS_Android *get_singleton(); GodotJavaWrapper *get_godot_java(); GodotIOJavaWrapper *get_godot_io_java(); - virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); - virtual bool request_permission(const String &p_name); - virtual bool request_permissions(); - virtual Vector<String> get_granted_permissions() const; + virtual bool request_permission(const String &p_name) override; + virtual bool request_permissions() override; + virtual Vector<String> get_granted_permissions() const override; - virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override; - virtual void set_mouse_show(bool p_show); - virtual void set_mouse_grab(bool p_grab); - virtual bool is_mouse_grab_enabled() const; - virtual Point2 get_mouse_position() const; - virtual int get_mouse_button_state() const; - virtual void set_window_title(const String &p_title); - - virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0); - virtual VideoMode get_video_mode(int p_screen = 0) const; - virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const; - - virtual void set_keep_screen_on(bool p_enabled); - - virtual Size2 get_window_size() const; - - virtual String get_name() const; - virtual MainLoop *get_main_loop() const; - - virtual bool can_draw() const; + virtual String get_name() const override; + virtual MainLoop *get_main_loop() const override; void main_loop_begin(); bool main_loop_iterate(); @@ -155,56 +100,28 @@ public: void main_loop_focusout(); void main_loop_focusin(); - virtual bool has_touchscreen_ui_hint() const; - - virtual bool has_virtual_keyboard() const; - virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2()); - virtual void hide_virtual_keyboard(); - virtual int get_virtual_keyboard_height() const; + void set_display_size(const Size2i &p_size); + Size2i get_display_size() const; + void set_context_is_16_bits(bool p_is_16); void set_opengl_extensions(const char *p_gl_extensions); - void set_display_size(Size2 p_size); - void set_context_is_16_bits(bool p_is_16); + void set_native_window(ANativeWindow *p_native_window); + ANativeWindow *get_native_window() const; + + virtual Error shell_open(String p_uri) override; + virtual String get_user_data_dir() const override; + virtual String get_resource_dir() const override; + virtual String get_locale() const override; + virtual String get_model_name() const override; + + virtual String get_unique_id() const override; + + virtual String get_system_dir(SystemDir p_dir) const override; + + void vibrate_handheld(int p_duration_ms) override; - virtual void set_screen_orientation(ScreenOrientation p_orientation); - - virtual Error shell_open(String p_uri); - virtual String get_user_data_dir() const; - virtual String get_resource_dir() const; - virtual String get_locale() const; - virtual void set_clipboard(const String &p_text); - virtual String get_clipboard() const; - virtual String get_model_name() const; - virtual int get_screen_dpi(int p_screen = 0) const; - - virtual String get_unique_id() const; - - virtual String get_system_dir(SystemDir p_dir) const; - - void process_accelerometer(const Vector3 &p_accelerometer); - void process_gravity(const Vector3 &p_gravity); - void process_magnetometer(const Vector3 &p_magnetometer); - void process_gyroscope(const Vector3 &p_gyroscope); - void process_touch(int p_what, int p_pointer, const Vector<TouchPos> &p_points); - void process_hover(int p_type, Point2 p_pos); - void process_double_tap(Point2 p_pos); - void process_scroll(Point2 p_pos); - void process_joy_event(JoypadEvent p_event); - void process_event(Ref<InputEvent> p_event); - void init_video_mode(int p_video_width, int p_video_height); - - virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track); - virtual bool native_video_is_playing() const; - virtual void native_video_pause(); - virtual void native_video_stop(); - - virtual bool is_joy_known(int p_device); - virtual String get_joy_guid(int p_device) const; - void joy_connection_changed(int p_device, bool p_connected, String p_name); - void vibrate_handheld(int p_duration_ms); - - virtual bool _check_internal_feature_support(const String &p_feature); + virtual bool _check_internal_feature_support(const String &p_feature) override; OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion); ~OS_Android(); }; diff --git a/platform/android/platform_config.h b/platform/android/platform_config.h index c5e896c4e1..601db9951f 100644 --- a/platform/android/platform_config.h +++ b/platform/android/platform_config.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/plugin/godot_plugin_config.h b/platform/android/plugin/godot_plugin_config.h new file mode 100644 index 0000000000..173ac115a2 --- /dev/null +++ b/platform/android/plugin/godot_plugin_config.h @@ -0,0 +1,269 @@ +/*************************************************************************/ +/* godot_plugin_config.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GODOT_PLUGIN_CONFIG_H +#define GODOT_PLUGIN_CONFIG_H + +#include "core/error/error_list.h" +#include "core/io/config_file.h" +#include "core/string/ustring.h" + +/* + The `config` section and fields are required and defined as follow: +- **name**: name of the plugin +- **binary_type**: can be either `local` or `remote`. The type affects the **binary** field +- **binary**: + - if **binary_type** is `local`, then this should be the filename of the plugin `aar` file in the `res://android/plugins` directory (e.g: `MyPlugin.aar`). + - if **binary_type** is `remote`, then this should be a declaration for a remote gradle binary (e.g: "org.godot.example:my-plugin:0.0.0"). + +The `dependencies` section and fields are optional and defined as follow: +- **local**: contains a list of local `.aar` binary files the plugin depends on. The local binary dependencies must also be located in the `res://android/plugins` directory. +- **remote**: contains a list of remote binary gradle dependencies for the plugin. +- **custom_maven_repos**: contains a list of urls specifying custom maven repos required for the plugin's dependencies. + + See https://github.com/godotengine/godot/issues/38157#issuecomment-618773871 + */ +struct PluginConfigAndroid { + inline static const char *PLUGIN_CONFIG_EXT = ".gdap"; + + inline static const char *CONFIG_SECTION = "config"; + inline static const char *CONFIG_NAME_KEY = "name"; + inline static const char *CONFIG_BINARY_TYPE_KEY = "binary_type"; + inline static const char *CONFIG_BINARY_KEY = "binary"; + + inline static const char *DEPENDENCIES_SECTION = "dependencies"; + inline static const char *DEPENDENCIES_LOCAL_KEY = "local"; + inline static const char *DEPENDENCIES_REMOTE_KEY = "remote"; + inline static const char *DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY = "custom_maven_repos"; + + inline static const char *BINARY_TYPE_LOCAL = "local"; + inline static const char *BINARY_TYPE_REMOTE = "remote"; + + inline static const char *PLUGIN_VALUE_SEPARATOR = "|"; + + // Set to true when the config file is properly loaded. + bool valid_config = false; + // Unix timestamp of last change to this plugin. + uint64_t last_updated = 0; + + // Required config section + String name; + String binary_type; + String binary; + + // Optional dependencies section + Vector<String> local_dependencies; + Vector<String> remote_dependencies; + Vector<String> custom_maven_repos; +}; + +/* + * Set of prebuilt plugins. + * Currently unused, this is just for future reference: + */ +// static const PluginConfigAndroid MY_PREBUILT_PLUGIN = { +// /*.valid_config =*/true, +// /*.last_updated =*/0, +// /*.name =*/"GodotPayment", +// /*.binary_type =*/"local", +// /*.binary =*/"res://android/build/libs/plugins/GodotPayment.release.aar", +// /*.local_dependencies =*/{}, +// /*.remote_dependencies =*/String("com.android.billingclient:billing:2.2.1").split("|"), +// /*.custom_maven_repos =*/{} +// }; + +static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) { + String absolute_path; + if (!dependency_path.is_empty()) { + if (dependency_path.is_abs_path()) { + absolute_path = ProjectSettings::get_singleton()->globalize_path(dependency_path); + } else { + absolute_path = plugin_config_dir.plus_file(dependency_path); + } + } + + return absolute_path; +} + +static inline PluginConfigAndroid resolve_prebuilt_plugin(PluginConfigAndroid prebuilt_plugin, String plugin_config_dir) { + PluginConfigAndroid resolved = prebuilt_plugin; + resolved.binary = resolved.binary_type == PluginConfigAndroid::BINARY_TYPE_LOCAL ? resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.binary) : prebuilt_plugin.binary; + if (!prebuilt_plugin.local_dependencies.is_empty()) { + resolved.local_dependencies.clear(); + for (int i = 0; i < prebuilt_plugin.local_dependencies.size(); i++) { + resolved.local_dependencies.push_back(resolve_local_dependency_path(plugin_config_dir, prebuilt_plugin.local_dependencies[i])); + } + } + return resolved; +} + +static inline Vector<PluginConfigAndroid> get_prebuilt_plugins(String plugins_base_dir) { + Vector<PluginConfigAndroid> prebuilt_plugins; + // prebuilt_plugins.push_back(resolve_prebuilt_plugin(MY_PREBUILT_PLUGIN, plugins_base_dir)); + return prebuilt_plugins; +} + +static inline bool is_plugin_config_valid(PluginConfigAndroid plugin_config) { + bool valid_name = !plugin_config.name.is_empty(); + bool valid_binary_type = plugin_config.binary_type == PluginConfigAndroid::BINARY_TYPE_LOCAL || + plugin_config.binary_type == PluginConfigAndroid::BINARY_TYPE_REMOTE; + + bool valid_binary = false; + if (valid_binary_type) { + valid_binary = !plugin_config.binary.is_empty() && + (plugin_config.binary_type == PluginConfigAndroid::BINARY_TYPE_REMOTE || + + FileAccess::exists(plugin_config.binary)); + } + + bool valid_local_dependencies = true; + if (!plugin_config.local_dependencies.is_empty()) { + for (int i = 0; i < plugin_config.local_dependencies.size(); i++) { + if (!FileAccess::exists(plugin_config.local_dependencies[i])) { + valid_local_dependencies = false; + break; + } + } + } + return valid_name && valid_binary && valid_binary_type && valid_local_dependencies; +} + +static inline uint64_t get_plugin_modification_time(const PluginConfigAndroid &plugin_config, const String &config_path) { + uint64_t last_updated = FileAccess::get_modified_time(config_path); + last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary)); + + for (int i = 0; i < plugin_config.local_dependencies.size(); i++) { + String binary = plugin_config.local_dependencies.get(i); + last_updated = MAX(last_updated, FileAccess::get_modified_time(binary)); + } + + return last_updated; +} + +static inline PluginConfigAndroid load_plugin_config(Ref<ConfigFile> config_file, const String &path) { + PluginConfigAndroid plugin_config = {}; + + if (config_file.is_valid()) { + Error err = config_file->load(path); + if (err == OK) { + String config_base_dir = path.get_base_dir(); + + plugin_config.name = config_file->get_value(PluginConfigAndroid::CONFIG_SECTION, PluginConfigAndroid::CONFIG_NAME_KEY, String()); + plugin_config.binary_type = config_file->get_value(PluginConfigAndroid::CONFIG_SECTION, PluginConfigAndroid::CONFIG_BINARY_TYPE_KEY, String()); + + String binary_path = config_file->get_value(PluginConfigAndroid::CONFIG_SECTION, PluginConfigAndroid::CONFIG_BINARY_KEY, String()); + plugin_config.binary = plugin_config.binary_type == PluginConfigAndroid::BINARY_TYPE_LOCAL ? resolve_local_dependency_path(config_base_dir, binary_path) : binary_path; + + if (config_file->has_section(PluginConfigAndroid::DEPENDENCIES_SECTION)) { + Vector<String> local_dependencies_paths = config_file->get_value(PluginConfigAndroid::DEPENDENCIES_SECTION, PluginConfigAndroid::DEPENDENCIES_LOCAL_KEY, Vector<String>()); + if (!local_dependencies_paths.is_empty()) { + for (int i = 0; i < local_dependencies_paths.size(); i++) { + plugin_config.local_dependencies.push_back(resolve_local_dependency_path(config_base_dir, local_dependencies_paths[i])); + } + } + + plugin_config.remote_dependencies = config_file->get_value(PluginConfigAndroid::DEPENDENCIES_SECTION, PluginConfigAndroid::DEPENDENCIES_REMOTE_KEY, Vector<String>()); + plugin_config.custom_maven_repos = config_file->get_value(PluginConfigAndroid::DEPENDENCIES_SECTION, PluginConfigAndroid::DEPENDENCIES_CUSTOM_MAVEN_REPOS_KEY, Vector<String>()); + } + + plugin_config.valid_config = is_plugin_config_valid(plugin_config); + plugin_config.last_updated = get_plugin_modification_time(plugin_config, path); + } + } + + return plugin_config; +} + +static inline String get_plugins_binaries(String binary_type, Vector<PluginConfigAndroid> plugins_configs) { + String plugins_binaries; + if (!plugins_configs.is_empty()) { + Vector<String> binaries; + for (int i = 0; i < plugins_configs.size(); i++) { + PluginConfigAndroid config = plugins_configs[i]; + if (!config.valid_config) { + continue; + } + + if (config.binary_type == binary_type) { + binaries.push_back(config.binary); + } + + if (binary_type == PluginConfigAndroid::BINARY_TYPE_LOCAL) { + binaries.append_array(config.local_dependencies); + } + + if (binary_type == PluginConfigAndroid::BINARY_TYPE_REMOTE) { + binaries.append_array(config.remote_dependencies); + } + } + + plugins_binaries = String(PluginConfigAndroid::PLUGIN_VALUE_SEPARATOR).join(binaries); + } + + return plugins_binaries; +} + +static inline String get_plugins_custom_maven_repos(Vector<PluginConfigAndroid> plugins_configs) { + String custom_maven_repos; + if (!plugins_configs.is_empty()) { + Vector<String> repos_urls; + for (int i = 0; i < plugins_configs.size(); i++) { + PluginConfigAndroid config = plugins_configs[i]; + if (!config.valid_config) { + continue; + } + + repos_urls.append_array(config.custom_maven_repos); + } + + custom_maven_repos = String(PluginConfigAndroid::PLUGIN_VALUE_SEPARATOR).join(repos_urls); + } + return custom_maven_repos; +} + +static inline String get_plugins_names(Vector<PluginConfigAndroid> plugins_configs) { + String plugins_names; + if (!plugins_configs.is_empty()) { + Vector<String> names; + for (int i = 0; i < plugins_configs.size(); i++) { + PluginConfigAndroid config = plugins_configs[i]; + if (!config.valid_config) { + continue; + } + + names.push_back(config.name); + } + plugins_names = String(PluginConfigAndroid::PLUGIN_VALUE_SEPARATOR).join(names); + } + + return plugins_names; +} + +#endif // GODOT_PLUGIN_CONFIG_H diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp new file mode 100644 index 0000000000..ba3e9fa20f --- /dev/null +++ b/platform/android/plugin/godot_plugin_jni.cpp @@ -0,0 +1,158 @@ +/*************************************************************************/ +/* godot_plugin_jni.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "godot_plugin_jni.h" + +#include <core/config/engine.h> +#include <core/config/project_settings.h> +#include <core/error/error_macros.h> +#include <platform/android/api/jni_singleton.h> +#include <platform/android/jni_utils.h> +#include <platform/android/string_android.h> + +static HashMap<String, JNISingleton *> jni_singletons; + +extern "C" { + +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSingleton(JNIEnv *env, jclass clazz, jstring name, jobject obj) { + String singname = jstring_to_string(name, env); + JNISingleton *s = (JNISingleton *)ClassDB::instance("JNISingleton"); + 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, jclass clazz, 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_nativeRegisterSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types) { + String singleton_name = jstring_to_string(j_plugin_name, env); + + ERR_FAIL_COND(!jni_singletons.has(singleton_name)); + + JNISingleton *singleton = jni_singletons.get(singleton_name); + + String signal_name = jstring_to_string(j_signal_name, env); + Vector<Variant::Type> types; + + int stringCount = env->GetArrayLength(j_signal_param_types); + + for (int i = 0; i < stringCount; i++) { + jstring j_signal_param_type = (jstring)env->GetObjectArrayElement(j_signal_param_types, i); + const String signal_param_type = jstring_to_string(j_signal_param_type, env); + types.push_back(get_jni_type(signal_param_type)); + } + + singleton->add_signal(signal_name, types); +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params) { + String singleton_name = jstring_to_string(j_plugin_name, env); + + ERR_FAIL_COND(!jni_singletons.has(singleton_name)); + + JNISingleton *singleton = jni_singletons.get(singleton_name); + + String signal_name = jstring_to_string(j_signal_name, env); + + int count = env->GetArrayLength(j_signal_params); + ERR_FAIL_COND_MSG(count > VARIANT_ARG_MAX, "Maximum argument count exceeded!"); + + Variant variant_params[VARIANT_ARG_MAX]; + const Variant *args[VARIANT_ARG_MAX]; + + for (int i = 0; i < count; i++) { + jobject j_param = env->GetObjectArrayElement(j_signal_params, i); + variant_params[i] = _jobject_to_variant(env, j_param); + args[i] = &variant_params[i]; + env->DeleteLocalRef(j_param); + }; + + singleton->emit_signal(signal_name, args, count); +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jclass clazz, jobjectArray gdnlib_paths) { + int gdnlib_count = env->GetArrayLength(gdnlib_paths); + if (gdnlib_count == 0) { + return; + } + + // 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..b87f922e03 --- /dev/null +++ b/platform/android/plugin/godot_plugin_jni.h @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* godot_plugin_jni.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef 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, jclass clazz, jstring name, jobject obj); +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterMethod(JNIEnv *env, jclass clazz, jstring sname, jstring name, jstring ret, jobjectArray args); +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types); +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_params); +JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jclass clazz, jobjectArray gdnlib_paths); +} + +#endif // GODOT_PLUGIN_JNI_H diff --git a/platform/android/power_android.cpp b/platform/android/power_android.cpp deleted file mode 100644 index b0a90312e5..0000000000 --- a/platform/android/power_android.cpp +++ /dev/null @@ -1,255 +0,0 @@ -/*************************************************************************/ -/* power_android.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. */ -/*************************************************************************/ - -/* -Adapted from corresponding SDL 2.0 code. -*/ - -/* - Simple DirectMedia Layer - Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org> - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -*/ - -#include "power_android.h" - -#include "core/error_macros.h" - -static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder) { - if (refholder->m_env) { - JNIEnv *env = refholder->m_env; - (*env)->PopLocalFrame(env, NULL); - --s_active; - } -} - -static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func) { - struct LocalReferenceHolder refholder; - refholder.m_env = NULL; - refholder.m_func = func; - return refholder; -} - -static bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env) { - const int capacity = 16; - if ((*env)->PushLocalFrame(env, capacity) < 0) { - return false; - } - ++s_active; - refholder->m_env = env; - return true; -} - -static SDL_bool LocalReferenceHolder_IsActive(void) { - return s_active > 0; -} - -ANativeWindow *Android_JNI_GetNativeWindow(void) { - ANativeWindow *anw; - jobject s; - JNIEnv *env = Android_JNI_GetEnv(); - - s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface); - anw = ANativeWindow_fromSurface(env, s); - (*env)->DeleteLocalRef(env, s); - - return anw; -} - -/* - * CODE CHUNK IMPORTED FROM SDL 2.0 - * returns 0 on success or -1 on error (others undefined then) - * returns truthy or falsy value in plugged, charged and battery - * returns the value in seconds and percent or -1 if not available - */ -int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent) { - env = Android_JNI_GetEnv(); - refs = LocalReferenceHolder_Setup(__FUNCTION__); - - if (!LocalReferenceHolder_Init(&refs, env)) { - LocalReferenceHolder_Cleanup(&refs); - return -1; - } - mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;"); - context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid); - action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED"); - cls = (*env)->FindClass(env, "android/content/IntentFilter"); - mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V"); - filter = (*env)->NewObject(env, cls, mid, action); - (*env)->DeleteLocalRef(env, action); - mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;"); - intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter); - (*env)->DeleteLocalRef(env, filter); - cls = (*env)->GetObjectClass(env, intent); - imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I"); -// Watch out for C89 scoping rules because of the macro -#define GET_INT_EXTRA(var, key) \ - int var; \ - iname = (*env)->NewStringUTF(env, key); \ - var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \ - (*env)->DeleteLocalRef(env, iname); - bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z"); -// Watch out for C89 scoping rules because of the macro -#define GET_BOOL_EXTRA(var, key) \ - int var; \ - bname = (*env)->NewStringUTF(env, key); \ - var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \ - (*env)->DeleteLocalRef(env, bname); - if (plugged) { - // Watch out for C89 scoping rules because of the macro - GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5) - if (plug == -1) { - LocalReferenceHolder_Cleanup(&refs); - return -1; - } - // 1 == BatteryManager.BATTERY_PLUGGED_AC - // 2 == BatteryManager.BATTERY_PLUGGED_USB - *plugged = (0 < plug) ? 1 : 0; - } - if (charged) { - // Watch out for C89 scoping rules because of the macro - GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5) - if (status == -1) { - LocalReferenceHolder_Cleanup(&refs); - return -1; - } - // 5 == BatteryManager.BATTERY_STATUS_FULL - *charged = (status == 5) ? 1 : 0; - } - if (battery) { - GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5) - *battery = present ? 1 : 0; - } - if (seconds) { - *seconds = -1; // not possible - } - if (percent) { - int level; - int scale; - // Watch out for C89 scoping rules because of the macro - { - GET_INT_EXTRA(level_temp, "level") // == BatteryManager.EXTRA_LEVEL (API 5) - level = level_temp; - } - // Watch out for C89 scoping rules because of the macro - { - GET_INT_EXTRA(scale_temp, "scale") // == BatteryManager.EXTRA_SCALE (API 5) - scale = scale_temp; - } - if ((level == -1) || (scale == -1)) { - LocalReferenceHolder_Cleanup(&refs); - return -1; - } - *percent = level * 100 / scale; - } - (*env)->DeleteLocalRef(env, intent); - LocalReferenceHolder_Cleanup(&refs); - - return 0; -} - -bool PowerAndroid::GetPowerInfo_Android() { - int battery; - int plugged; - int charged; - - if (Android_JNI_GetPowerInfo(&plugged, &charged, &battery, &this->nsecs_left, &this->percent_left) != -1) { - if (plugged) { - if (charged) { - this->power_state = OS::POWERSTATE_CHARGED; - } else if (battery) { - this->power_state = OS::POWERSTATE_CHARGING; - } else { - this->power_state = OS::POWERSTATE_NO_BATTERY; - this->nsecs_left = -1; - this->percent_left = -1; - } - } else { - this->power_state = OS::POWERSTATE_ON_BATTERY; - } - } else { - this->power_state = OS::POWERSTATE_UNKNOWN; - this->nsecs_left = -1; - this->percent_left = -1; - } - - return true; -} - -OS::PowerState PowerAndroid::get_power_state() { - if (GetPowerInfo_Android()) { - return power_state; - } else { - WARN_PRINT("Power management is not implemented on this platform, defaulting to POWERSTATE_UNKNOWN"); - return OS::POWERSTATE_UNKNOWN; - } -} - -int PowerAndroid::get_power_seconds_left() { - if (GetPowerInfo_Android()) { - return nsecs_left; - } else { - WARN_PRINT("Power management is not implemented on this platform, defaulting to -1"); - return -1; - } -} - -int PowerAndroid::get_power_percent_left() { - if (GetPowerInfo_Android()) { - return percent_left; - } else { - WARN_PRINT("Power management is not implemented on this platform, defaulting to -1"); - return -1; - } -} - -PowerAndroid::PowerAndroid() : - nsecs_left(-1), - percent_left(-1), - power_state(OS::POWERSTATE_UNKNOWN) { -} - -PowerAndroid::~PowerAndroid() { -} diff --git a/platform/android/string_android.h b/platform/android/string_android.h index 51c488c8cc..3721315d3f 100644 --- a/platform/android/string_android.h +++ b/platform/android/string_android.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,23 +30,23 @@ #ifndef STRING_ANDROID_H #define STRING_ANDROID_H -#include "core/ustring.h" +#include "core/string/ustring.h" #include "thread_jandroid.h" #include <jni.h> /** * Converts JNI jstring to Godot String. * @param source Source JNI string. If null an empty string is returned. - * @param env JNI environment instance. If null obtained by ThreadAndroid::get_env(). + * @param env JNI environment instance. If null obtained by get_jni_env(). * @return Godot string instance. */ -static inline String jstring_to_string(jstring source, JNIEnv *env = NULL) { +static inline String jstring_to_string(jstring source, JNIEnv *env = nullptr) { String result; if (source) { if (!env) { - env = ThreadAndroid::get_env(); + env = get_jni_env(); } - const char *const source_utf8 = env->GetStringUTFChars(source, NULL); + const char *const source_utf8 = env->GetStringUTFChars(source, nullptr); if (source_utf8) { result.parse_utf8(source_utf8); env->ReleaseStringUTFChars(source, source_utf8); diff --git a/platform/android/thread_jandroid.cpp b/platform/android/thread_jandroid.cpp index 98b61ad755..afcc7294f2 100644 --- a/platform/android/thread_jandroid.cpp +++ b/platform/android/thread_jandroid.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,127 +30,29 @@ #include "thread_jandroid.h" -#include "core/os/memory.h" -#include "core/safe_refcount.h" -#include "core/script_language.h" +#include "core/os/thread.h" -static void _thread_id_key_destr_callback(void *p_value) { - memdelete(static_cast<Thread::ID *>(p_value)); -} - -static pthread_key_t _create_thread_id_key() { - pthread_key_t key; - pthread_key_create(&key, &_thread_id_key_destr_callback); - return key; -} - -pthread_key_t ThreadAndroid::thread_id_key = _create_thread_id_key(); -Thread::ID ThreadAndroid::next_thread_id = 0; - -Thread::ID ThreadAndroid::get_id() const { - - return id; -} - -Thread *ThreadAndroid::create_thread_jandroid() { - - return memnew(ThreadAndroid); -} - -void *ThreadAndroid::thread_callback(void *userdata) { - - ThreadAndroid *t = reinterpret_cast<ThreadAndroid *>(userdata); - setup_thread(); - ScriptServer::thread_enter(); //scripts may need to attach a stack - t->id = atomic_increment(&next_thread_id); - pthread_setspecific(thread_id_key, (void *)memnew(ID(t->id))); - t->callback(t->user); - ScriptServer::thread_exit(); - return NULL; -} - -Thread *ThreadAndroid::create_func_jandroid(ThreadCreateCallback p_callback, void *p_user, const Settings &) { - - ThreadAndroid *tr = memnew(ThreadAndroid); - tr->callback = p_callback; - tr->user = p_user; - pthread_attr_init(&tr->pthread_attr); - pthread_attr_setdetachstate(&tr->pthread_attr, PTHREAD_CREATE_JOINABLE); - - pthread_create(&tr->pthread, &tr->pthread_attr, thread_callback, tr); - - return tr; -} +static JavaVM *java_vm = nullptr; +static thread_local JNIEnv *env = nullptr; -Thread::ID ThreadAndroid::get_thread_id_func_jandroid() { - - void *value = pthread_getspecific(thread_id_key); - - if (value) - return *static_cast<ID *>(value); - - ID new_id = atomic_increment(&next_thread_id); - pthread_setspecific(thread_id_key, (void *)memnew(ID(new_id))); - return new_id; +static void init_thread() { + java_vm->AttachCurrentThread(&env, nullptr); } -void ThreadAndroid::wait_to_finish_func_jandroid(Thread *p_thread) { - - ThreadAndroid *tp = static_cast<ThreadAndroid *>(p_thread); - ERR_FAIL_COND(!tp); - ERR_FAIL_COND(tp->pthread == 0); - - pthread_join(tp->pthread, NULL); - tp->pthread = 0; +static void term_thread() { + java_vm->DetachCurrentThread(); } -void ThreadAndroid::_thread_destroyed(void *value) { - - /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */ - JNIEnv *env = (JNIEnv *)value; - if (env != NULL) { - java_vm->DetachCurrentThread(); - pthread_setspecific(jvm_key, NULL); - } -} - -pthread_key_t ThreadAndroid::jvm_key; -JavaVM *ThreadAndroid::java_vm = NULL; - -void ThreadAndroid::setup_thread() { - - if (pthread_getspecific(jvm_key)) - return; //already setup - JNIEnv *env; - java_vm->AttachCurrentThread(&env, NULL); - pthread_setspecific(jvm_key, (void *)env); +void init_thread_jandroid(JavaVM *p_jvm, JNIEnv *p_env) { + java_vm = p_jvm; + env = p_env; + Thread::_set_platform_funcs(nullptr, nullptr, &init_thread, &term_thread); } -void ThreadAndroid::make_default(JavaVM *p_java_vm) { - - java_vm = p_java_vm; - create_func = create_func_jandroid; - get_thread_id_func = get_thread_id_func_jandroid; - wait_to_finish_func = wait_to_finish_func_jandroid; - pthread_key_create(&jvm_key, _thread_destroyed); - setup_thread(); +void setup_android_thread() { + init_thread(); } -JNIEnv *ThreadAndroid::get_env() { - - if (!pthread_getspecific(jvm_key)) { - setup_thread(); - } - - JNIEnv *env = NULL; - java_vm->AttachCurrentThread(&env, NULL); +JNIEnv *get_jni_env() { return env; } - -ThreadAndroid::ThreadAndroid() { - - pthread = 0; -} - -ThreadAndroid::~ThreadAndroid() { -} diff --git a/platform/android/thread_jandroid.h b/platform/android/thread_jandroid.h index eb4725ae68..ff13ae911f 100644 --- a/platform/android/thread_jandroid.h +++ b/platform/android/thread_jandroid.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,47 +28,14 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef THREAD_POSIX_H -#define THREAD_POSIX_H +#ifndef THREAD_JANDROID_H +#define THREAD_JANDROID_H -#include "core/os/thread.h" #include <jni.h> -#include <pthread.h> -#include <sys/types.h> -class ThreadAndroid : public Thread { +void init_thread_jandroid(JavaVM *p_jvm, JNIEnv *p_env); - static pthread_key_t thread_id_key; - static ID next_thread_id; - - pthread_t pthread; - pthread_attr_t pthread_attr; - ThreadCreateCallback callback; - void *user; - ID id; - - static Thread *create_thread_jandroid(); - - static void *thread_callback(void *userdata); - - static Thread *create_func_jandroid(ThreadCreateCallback p_callback, void *, const Settings &); - static ID get_thread_id_func_jandroid(); - static void wait_to_finish_func_jandroid(Thread *p_thread); - - static void _thread_destroyed(void *value); - ThreadAndroid(); - - static pthread_key_t jvm_key; - static JavaVM *java_vm; - -public: - virtual ID get_id() const; - - static void make_default(JavaVM *p_java_vm); - static void setup_thread(); - static JNIEnv *get_env(); - - ~ThreadAndroid(); -}; +void setup_android_thread(); +JNIEnv *get_jni_env(); #endif diff --git a/platform/android/vulkan/vulkan_context_android.cpp b/platform/android/vulkan/vulkan_context_android.cpp new file mode 100644 index 0000000000..1bf85f07f1 --- /dev/null +++ b/platform/android/vulkan/vulkan_context_android.cpp @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* vulkan_context_android.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "vulkan_context_android.h" + +#include <vulkan/vulkan_android.h> + +const char *VulkanContextAndroid::_get_platform_surface_extension() const { + return VK_KHR_ANDROID_SURFACE_EXTENSION_NAME; +} + +int VulkanContextAndroid::window_create(ANativeWindow *p_window, int p_width, int p_height) { + VkAndroidSurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = nullptr; + createInfo.flags = 0; + createInfo.window = p_window; + + VkSurfaceKHR surface; + VkResult err = vkCreateAndroidSurfaceKHR(_get_instance(), &createInfo, nullptr, &surface); + if (err != VK_SUCCESS) { + ERR_FAIL_V_MSG(-1, "vkCreateAndroidSurfaceKHR failed with error " + itos(err)); + } + + return _window_create(DisplayServer::MAIN_WINDOW_ID, surface, p_width, p_height); +} + +VulkanContextAndroid::VulkanContextAndroid() { + // TODO: fix validation layers + use_validation_layers = false; +} + +VulkanContextAndroid::~VulkanContextAndroid() { +} diff --git a/platform/android/power_android.h b/platform/android/vulkan/vulkan_context_android.h index 9f77f3fc6b..c608f2d665 100644 --- a/platform/android/power_android.h +++ b/platform/android/vulkan/vulkan_context_android.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* power_android.h */ +/* vulkan_context_android.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -28,53 +28,21 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef POWER_ANDROID_H -#define POWER_ANDROID_H +#ifndef VULKAN_CONTEXT_ANDROID_H +#define VULKAN_CONTEXT_ANDROID_H -#include "core/os/os.h" +#include "drivers/vulkan/vulkan_context.h" -#include <android/native_window_jni.h> +struct ANativeWindow; -class PowerAndroid { - - struct LocalReferenceHolder { - JNIEnv *m_env; - const char *m_func; - }; - -private: - static struct LocalReferenceHolder refs; - static JNIEnv *env; - static jmethodID mid; - static jobject context; - static jstring action; - static jclass cls; - static jobject filter; - static jobject intent; - static jstring iname; - static jmethodID imid; - static jstring bname; - static jmethodID bmid; - - int nsecs_left; - int percent_left; - OS::PowerState power_state; - - bool GetPowerInfo_Android(); - bool UpdatePowerInfo(); +class VulkanContextAndroid : public VulkanContext { + virtual const char *_get_platform_surface_extension() const; public: - static int s_active; - - PowerAndroid(); - virtual ~PowerAndroid(); - static bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env); - static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func); - static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder); + int window_create(ANativeWindow *p_window, int p_width, int p_height); - OS::PowerState get_power_state(); - int get_power_seconds_left(); - int get_power_percent_left(); + VulkanContextAndroid(); + ~VulkanContextAndroid(); }; -#endif // POWER_ANDROID_H +#endif // VULKAN_CONTEXT_ANDROID_H |