From c6a23a7a7d3f0747ccfdc11a56fc04f57feb867f Mon Sep 17 00:00:00 2001 From: Fredia Huya-Kouadio Date: Wed, 3 Jul 2024 22:32:47 -0700 Subject: [PATCH 1/2] Fix crashes reported by the Google Play Console --- main/main.cpp | 22 +++--- platform/android/java/app/config.gradle | 2 +- platform/android/java/editor/build.gradle | 2 +- .../lib/src/org/godotengine/godot/Godot.kt | 76 ++++++++++--------- .../org/godotengine/godot/GodotFragment.java | 7 ++ .../src/org/godotengine/godot/GodotIO.java | 2 +- .../godotengine/godot/utils/BenchmarkUtils.kt | 11 ++- 7 files changed, 68 insertions(+), 54 deletions(-) diff --git a/main/main.cpp b/main/main.cpp index 9743a8086f..0209f1cc4f 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -909,13 +909,11 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph // Benchmark tracking must be done after `OS::get_singleton()->initialize()` as on some // platforms, it's used to set up the time utilities. - OS::get_singleton()->benchmark_begin_measure("Startup", "Total"); - OS::get_singleton()->benchmark_begin_measure("Startup", "Setup"); + OS::get_singleton()->benchmark_begin_measure("Startup", "Main::Setup"); engine = memnew(Engine); MAIN_PRINT("Main: Initialize CORE"); - OS::get_singleton()->benchmark_begin_measure("Startup", "Core"); register_core_types(); register_core_driver_types(); @@ -2453,8 +2451,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph Thread::release_main_thread(); // If setup2() is called from another thread, that one will become main thread, so preventively release this one. set_current_thread_safe_for_nodes(false); - OS::get_singleton()->benchmark_end_measure("Startup", "Core"); - #if defined(STEAMAPI_ENABLED) if (editor || project_manager) { steam_tracker = memnew(SteamTracker); @@ -2465,7 +2461,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph return setup2(); } - OS::get_singleton()->benchmark_end_measure("Startup", "Setup"); + OS::get_singleton()->benchmark_end_measure("Startup", "Main::Setup"); return OK; error: @@ -2519,7 +2515,7 @@ error: } OS::get_singleton()->benchmark_end_measure("Startup", "Core"); - OS::get_singleton()->benchmark_end_measure("Startup", "Setup"); + OS::get_singleton()->benchmark_end_measure("Startup", "Main::Setup"); #if defined(STEAMAPI_ENABLED) if (steam_tracker) { @@ -2553,6 +2549,8 @@ Error _parse_resource_dummy(void *p_data, VariantParser::Stream *p_stream, Refbenchmark_begin_measure("Startup", "Main::Setup2"); + Thread::make_main_thread(); // Make whatever thread call this the main thread. set_current_thread_safe_for_nodes(true); @@ -3149,7 +3147,7 @@ Error Main::setup2(bool p_show_boot_logo) { print_verbose("EDITOR API HASH: " + uitos(ClassDB::get_api_hash(ClassDB::API_EDITOR))); MAIN_PRINT("Main: Done"); - OS::get_singleton()->benchmark_end_measure("Startup", "Setup"); + OS::get_singleton()->benchmark_end_measure("Startup", "Main::Setup2"); return OK; } @@ -3230,6 +3228,8 @@ static MainTimerSync main_timer_sync; // and should move on to `OS::run`, and EXIT_FAILURE otherwise for // an early exit with that error code. int Main::start() { + OS::get_singleton()->benchmark_begin_measure("Startup", "Main::Start"); + ERR_FAIL_COND_V(!_start_success, false); bool has_icon = false; @@ -3953,7 +3953,7 @@ int Main::start() { } } - OS::get_singleton()->benchmark_end_measure("Startup", "Total"); + OS::get_singleton()->benchmark_end_measure("Startup", "Main::Start"); OS::get_singleton()->benchmark_dump(); return EXIT_SUCCESS; @@ -4221,7 +4221,7 @@ void Main::force_redraw() { * The order matters as some of those steps are linked with each other. */ void Main::cleanup(bool p_force) { - OS::get_singleton()->benchmark_begin_measure("Shutdown", "Total"); + OS::get_singleton()->benchmark_begin_measure("Shutdown", "Main::Cleanup"); if (!p_force) { ERR_FAIL_COND(!_start_success); } @@ -4379,7 +4379,7 @@ void Main::cleanup(bool p_force) { unregister_core_types(); - OS::get_singleton()->benchmark_end_measure("Shutdown", "Total"); + OS::get_singleton()->benchmark_end_measure("Shutdown", "Main::Cleanup"); OS::get_singleton()->benchmark_dump(); OS::get_singleton()->finalize_core(); diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 01759a1b2f..eb9ad9de05 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -7,7 +7,7 @@ ext.versions = [ targetSdk : 34, buildTools : '34.0.0', kotlinVersion : '1.9.20', - fragmentVersion : '1.6.2', + fragmentVersion : '1.7.1', nexusPublishVersion: '1.3.0', javaVersion : JavaVersion.VERSION_17, // Also update 'platform/android/detect.py#get_ndk_version()' when this is updated. diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle index 55fe2a22fe..37f68d295a 100644 --- a/platform/android/java/editor/build.gradle +++ b/platform/android/java/editor/build.gradle @@ -9,7 +9,7 @@ dependencies { implementation "androidx.fragment:fragment:$versions.fragmentVersion" implementation project(":lib") - implementation "androidx.window:window:1.2.0" + implementation "androidx.window:window:1.3.0" implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion" implementation "androidx.constraintlayout:constraintlayout:2.1.4" } diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 290be727ab..2bd5e2b315 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -86,15 +86,14 @@ class Godot(private val context: Context) : SensorEventListener { private val TAG = Godot::class.java.simpleName } - private val windowManager: WindowManager by lazy { - requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager - } + private val windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + private val mSensorManager: SensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager + private val mClipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + private val vibratorService: Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + private val pluginRegistry: GodotPluginRegistry by lazy { GodotPluginRegistry.getPluginRegistry() } - private val mSensorManager: SensorManager by lazy { - requireActivity().getSystemService(Context.SENSOR_SERVICE) as SensorManager - } private val mAccelerometer: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) } @@ -107,9 +106,6 @@ class Godot(private val context: Context) : SensorEventListener { private val mGyroscope: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) } - private val mClipboard: ClipboardManager by lazy { - requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - } private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int -> if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) { @@ -323,13 +319,15 @@ class Godot(private val context: Context) : SensorEventListener { return false } - if (expansionPackPath.isNotEmpty()) { - commandLine.add("--main-pack") - commandLine.add(expansionPackPath) - } - val activity = requireActivity() - if (!nativeLayerInitializeCompleted) { - nativeLayerInitializeCompleted = GodotLib.initialize( + beginBenchmarkMeasure("Startup", "Godot::onInitNativeLayer") + try { + if (expansionPackPath.isNotEmpty()) { + commandLine.add("--main-pack") + commandLine.add(expansionPackPath) + } + val activity = requireActivity() + if (!nativeLayerInitializeCompleted) { + nativeLayerInitializeCompleted = GodotLib.initialize( activity, this, activity.assets, @@ -338,15 +336,17 @@ class Godot(private val context: Context) : SensorEventListener { directoryAccessHandler, fileAccessHandler, useApkExpansion, - ) - } - - if (nativeLayerInitializeCompleted && !nativeLayerSetupCompleted) { - nativeLayerSetupCompleted = GodotLib.setup(commandLine.toTypedArray(), tts) - if (!nativeLayerSetupCompleted) { - Log.e(TAG, "Unable to setup the Godot engine! Aborting...") - alert(R.string.error_engine_setup_message, R.string.text_error_title, this::forceQuit) + ) } + + if (nativeLayerInitializeCompleted && !nativeLayerSetupCompleted) { + nativeLayerSetupCompleted = GodotLib.setup(commandLine.toTypedArray(), tts) + if (!nativeLayerSetupCompleted) { + throw IllegalStateException("Unable to setup the Godot engine! Aborting...") + } + } + } finally { + endBenchmarkMeasure("Startup", "Godot::onInitNativeLayer") } return isNativeInitialized() } @@ -370,6 +370,7 @@ class Godot(private val context: Context) : SensorEventListener { throw IllegalStateException("onInitNativeLayer() must be invoked successfully prior to initializing the render view") } + beginBenchmarkMeasure("Startup", "Godot::onInitRenderView") try { val activity: Activity = host.activity containerLayout = providedContainerLayout @@ -392,8 +393,7 @@ class Godot(private val context: Context) : SensorEventListener { containerLayout?.addView(editText) renderView = if (usesVulkan()) { if (!meetsVulkanRequirements(activity.packageManager)) { - alert(R.string.error_missing_vulkan_requirements_message, R.string.text_error_title, this::forceQuit) - return null + throw IllegalStateException(activity.getString(R.string.error_missing_vulkan_requirements_message)) } GodotVulkanRenderView(host, this) } else { @@ -482,6 +482,8 @@ class Godot(private val context: Context) : SensorEventListener { containerLayout?.removeAllViews() containerLayout = null } + + endBenchmarkMeasure("Startup", "Godot::onInitRenderView") } return containerLayout } @@ -609,13 +611,17 @@ class Godot(private val context: Context) : SensorEventListener { // These properties are defined after Godot setup completion, so we retrieve them here. val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click")) val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) - val rotaryInputAxis = java.lang.Integer.parseInt(GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis")) + val rotaryInputAxisValue = GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis") runOnUiThread { renderView?.inputHandler?.apply { enableLongPress(longPressEnabled) enablePanningAndScalingGestures(panScaleEnabled) - setRotaryInputAxis(rotaryInputAxis) + try { + setRotaryInputAxis(Integer.parseInt(rotaryInputAxisValue)) + } catch (e: NumberFormatException) { + Log.w(TAG, e) + } } } @@ -646,12 +652,7 @@ class Godot(private val context: Context) : SensorEventListener { decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener) } - @Keep - private fun alert(message: String, title: String) { - alert(message, title, null) - } - - private fun alert( + fun alert( @StringRes messageResId: Int, @StringRes titleResId: Int, okCallback: Runnable? @@ -660,7 +661,9 @@ class Godot(private val context: Context) : SensorEventListener { alert(res.getString(messageResId), res.getString(titleResId), okCallback) } - private fun alert(message: String, title: String, okCallback: Runnable?) { + @JvmOverloads + @Keep + fun alert(message: String, title: String, okCallback: Runnable? = null) { val activity: Activity = getActivity() ?: return runOnUiThread { val builder = AlertDialog.Builder(activity) @@ -770,7 +773,7 @@ class Godot(private val context: Context) : SensorEventListener { mClipboard.setPrimaryClip(clip) } - private fun forceQuit() { + fun forceQuit() { forceQuit(0) } @@ -881,7 +884,6 @@ class Godot(private val context: Context) : SensorEventListener { @Keep private fun vibrate(durationMs: Int, amplitude: Int) { if (durationMs > 0 && requestPermission("VIBRATE")) { - val vibratorService = getActivity()?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator? ?: return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (amplitude <= -1) { vibratorService.vibrate( diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java index a323045e1b..1612ddd0b3 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java @@ -42,6 +42,7 @@ import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.os.Messenger; +import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -203,6 +204,12 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH if (godotContainerLayout == null) { throw new IllegalStateException("Unable to initialize engine render view"); } + } catch (IllegalStateException e) { + Log.e(TAG, "Engine initialization failed", e); + final String errorMessage = TextUtils.isEmpty(e.getMessage()) + ? getString(R.string.error_engine_setup_message) + : e.getMessage(); + godot.alert(errorMessage, getString(R.string.text_error_title), godot::forceQuit); } catch (IllegalArgumentException ignored) { final Activity activity = getActivity(); Intent notifierIntent = new Intent(activity, activity.getClass()); 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 4b51bd778d..219631284a 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -121,7 +121,7 @@ public class GodotIO { activity.startActivity(intent); return 0; - } catch (ActivityNotFoundException e) { + } catch (Exception e) { Log.e(TAG, "Unable to open uri " + uriString, e); return 1; } diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt index 69748c0a8d..d39f2309b8 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt @@ -81,7 +81,8 @@ fun beginBenchmarkMeasure(scope: String, label: String) { * * * Note: Only enabled on 'editorDev' build variant. */ -fun endBenchmarkMeasure(scope: String, label: String) { +@JvmOverloads +fun endBenchmarkMeasure(scope: String, label: String, dumpBenchmark: Boolean = false) { if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") { return } @@ -93,6 +94,10 @@ fun endBenchmarkMeasure(scope: String, label: String) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { Trace.endAsyncSection("[$scope] $label", 0) } + + if (dumpBenchmark) { + dumpBenchmark() + } } /** @@ -102,11 +107,11 @@ fun endBenchmarkMeasure(scope: String, label: String) { * * Note: Only enabled on 'editorDev' build variant. */ @JvmOverloads -fun dumpBenchmark(fileAccessHandler: FileAccessHandler?, filepath: String? = benchmarkFile) { +fun dumpBenchmark(fileAccessHandler: FileAccessHandler? = null, filepath: String? = benchmarkFile) { if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") { return } - if (!useBenchmark) { + if (!useBenchmark || benchmarkTracker.isEmpty()) { return } From 6b6428d779c8327ec051ab0b5114a2eaa3f1a3bf Mon Sep 17 00:00:00 2001 From: Fredia Huya-Kouadio Date: Thu, 9 May 2024 10:22:26 -0700 Subject: [PATCH 2/2] Fix ANRs reported by the Google Play Console - Add support for dispatching input on the render thread (UI thread is the current default) when `input_buffering` and `accumulated_input` are disabled. At the expense of latency, this helps prevent 'heavy' applications / games from blocking the UI thread (the default behavior) which may cause the application to ANR. - Remove GLSurfaceView logic causing the UI thread to wait on the GL thread during lifecycle events. The removed logic would cause the UI thread to ANR when the GL thread is blocked. --- doc/classes/ProjectSettings.xml | 6 + editor/editor_settings.cpp | 5 + main/main.cpp | 2 + platform/android/java/app/AndroidManifest.xml | 4 + .../org/godotengine/editor/GodotEditor.kt | 12 ++ .../java/org/godotengine/editor/GodotGame.kt | 10 +- .../lib/src/org/godotengine/godot/Godot.kt | 60 ++++++-- .../src/org/godotengine/godot/GodotLib.java | 7 + .../godotengine/godot/gl/GLSurfaceView.java | 49 ------ .../godot/input/GodotGestureHandler.kt | 36 ++--- .../godot/input/GodotInputHandler.java | 139 ++++++++++++++---- .../godot/input/GodotTextInputWrapper.java | 18 +-- platform/android/java_godot_lib_jni.cpp | 7 + platform/android/java_godot_lib_jni.h | 1 + 14 files changed, 234 insertions(+), 122 deletions(-) diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index b1556783d5..5c97a975fe 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1391,6 +1391,12 @@ Enabling this can greatly improve the responsiveness to input, specially in devices that need to run multiple physics frames per visible (process) frame, because they can't run at the target frame rate. [b]Note:[/b] Currently implemented only on Android. + + If [code]true[/code], multiple input events will be accumulated into a single input event when possible. + + + If [code]true[/code], input events will be buffered prior to being dispatched. + If [code]true[/code], [method Input.is_action_just_pressed] and [method Input.is_action_just_released] will only return [code]true[/code] if the action is still in the respective state, i.e. an action that is pressed [i]and[/i] released on the same frame will be missed. If [code]false[/code], no input will be lost. diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index e021be9668..85e60df1dd 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -474,6 +474,11 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/vsync_mode", 1, "Disabled,Enabled,Adaptive,Mailbox") EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/update_continuously", false, "") +#ifdef ANDROID_ENABLED + EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/android/use_accumulated_input", true, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/android/use_input_buffering", true, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) +#endif + // Inspector EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "interface/inspector/max_array_dictionary_items_per_page", 20, "10,100,1") EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/inspector/show_low_level_opentype_features", false, "") diff --git a/main/main.cpp b/main/main.cpp index 0209f1cc4f..e6be23034d 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2939,6 +2939,8 @@ Error Main::setup2(bool p_show_boot_logo) { id->set_emulate_mouse_from_touch(bool(GLOBAL_DEF_BASIC("input_devices/pointing/emulate_mouse_from_touch", true))); } + GLOBAL_DEF("input_devices/buffering/android/use_accumulated_input", true); + GLOBAL_DEF("input_devices/buffering/android/use_input_buffering", true); GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_long_press_as_right_click", false); GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_pan_and_scale_gestures", false); GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "input_devices/pointing/android/rotary_input_scroll_axis", PROPERTY_HINT_ENUM, "Horizontal,Vertical"), 1); diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 4abc6548bf..0cc929d226 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -24,6 +24,10 @@ android:hasFragileUserData="false" android:requestLegacyExternalStorage="false" tools:ignore="GoogleAppIndexingWarning" > + GodotLib.dispatchMouseEvent(eventAction, updatedButtonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY)); + } else { + GodotLib.dispatchMouseEvent(eventAction, updatedButtonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY); + } return true; } } return false; } - static boolean handleTouchEvent(final MotionEvent event) { + boolean handleTouchEvent(final MotionEvent event) { return handleTouchEvent(event, event.getActionMasked()); } - static boolean handleTouchEvent(final MotionEvent event, int eventActionOverride) { + boolean handleTouchEvent(final MotionEvent event, int eventActionOverride) { return handleTouchEvent(event, eventActionOverride, false); } - static boolean handleTouchEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) { + boolean handleTouchEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) { final int pointerCount = event.getPointerCount(); if (pointerCount == 0) { return true; @@ -636,10 +661,70 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_POINTER_DOWN: { - GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap); + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap)); + } else { + GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap); + } return true; } } return false; } + + void handleMagnifyEvent(float x, float y, float factor) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.magnify(x, y, factor)); + } else { + GodotLib.magnify(x, y, factor); + } + } + + void handlePanEvent(float x, float y, float deltaX, float deltaY) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.pan(x, y, deltaX, deltaY)); + } else { + GodotLib.pan(x, y, deltaX, deltaY); + } + } + + private void handleJoystickButtonEvent(int device, int button, boolean pressed) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.joybutton(device, button, pressed)); + } else { + GodotLib.joybutton(device, button, pressed); + } + } + + private void handleJoystickAxisEvent(int device, int axis, float value) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.joyaxis(device, axis, value)); + } else { + GodotLib.joyaxis(device, axis, value); + } + } + + private void handleJoystickHatEvent(int device, int hatX, int hatY) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.joyhat(device, hatX, hatY)); + } else { + GodotLib.joyhat(device, hatX, hatY); + } + } + + private void handleJoystickConnectionChangedEvent(int device, boolean connected, String name) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.joyconnectionchanged(device, connected, name)); + } else { + GodotLib.joyconnectionchanged(device, connected, name); + } + } + + void handleKeyEvent(int physicalKeycode, int unicode, int keyLabel, boolean pressed, boolean echo) { + if (shouldDispatchInputToRenderThread()) { + mRenderView.queueOnRenderThread(() -> GodotLib.key(physicalKeycode, unicode, keyLabel, pressed, echo)); + } else { + GodotLib.key(physicalKeycode, unicode, keyLabel, pressed, echo); + } + } } 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 06b565c30f..e545669970 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 @@ -93,8 +93,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene @Override public void beforeTextChanged(final CharSequence pCharSequence, final int start, final int count, final int after) { for (int i = 0; i < count; ++i) { - GodotLib.key(KeyEvent.KEYCODE_DEL, 0, 0, true, false); - GodotLib.key(KeyEvent.KEYCODE_DEL, 0, 0, false, false); + mRenderView.getInputHandler().handleKeyEvent(KeyEvent.KEYCODE_DEL, 0, 0, true, false); + mRenderView.getInputHandler().handleKeyEvent(KeyEvent.KEYCODE_DEL, 0, 0, false, false); if (mHasSelection) { mHasSelection = false; @@ -115,8 +115,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene // Return keys are handled through action events continue; } - GodotLib.key(0, character, 0, true, false); - GodotLib.key(0, character, 0, false, false); + mRenderView.getInputHandler().handleKeyEvent(0, character, 0, true, false); + mRenderView.getInputHandler().handleKeyEvent(0, character, 0, false, false); } } @@ -127,18 +127,16 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene if (characters != null) { for (int i = 0; i < characters.length(); i++) { final int character = characters.codePointAt(i); - GodotLib.key(0, character, 0, true, false); - GodotLib.key(0, character, 0, false, false); + mRenderView.getInputHandler().handleKeyEvent(0, character, 0, true, false); + mRenderView.getInputHandler().handleKeyEvent(0, character, 0, false, false); } } } if (pActionID == EditorInfo.IME_ACTION_DONE) { // Enter key has been pressed - mRenderView.queueOnRenderThread(() -> { - GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, 0, true, false); - GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, 0, false, false); - }); + mRenderView.getInputHandler().handleKeyEvent(KeyEvent.KEYCODE_ENTER, 0, 0, true, false); + mRenderView.getInputHandler().handleKeyEvent(KeyEvent.KEYCODE_ENTER, 0, 0, false, false); mRenderView.getView().requestFocus(); return true; } diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 8493a8e932..87d4281c5a 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -549,4 +549,11 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIE os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED); } } + +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateInputDispatchSettings(JNIEnv *env, jclass clazz, jboolean p_use_accumulated_input, jboolean p_use_input_buffering) { + if (Input::get_singleton()) { + Input::get_singleton()->set_use_accumulated_input(p_use_accumulated_input); + Input::get_singleton()->set_use_input_buffering(p_use_input_buffering); + } +} } diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index f32ffc291a..852c475e7e 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -69,6 +69,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz); 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); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateInputDispatchSettings(JNIEnv *env, jclass clazz, jboolean p_use_accumulated_input, jboolean p_use_input_buffering); } #endif // JAVA_GODOT_LIB_JNI_H