From fc3a96e47a2a24b4df1ca4e1012841a078aa4847 Mon Sep 17 00:00:00 2001
From: Sylvain Becker <[EMAIL REDACTED]>
Date: Thu, 4 Jun 2026 05:38:04 +0200
Subject: [PATCH] Android: remove pollInputDevice() in favor of
InputDeviceListener (#15659)
---
android-project/app/proguard-rules.pro | 4 +-
.../main/java/org/libsdl/app/SDLActivity.java | 8 +-
.../org/libsdl/app/SDLControllerManager.java | 110 ++++++++++--------
src/core/android/SDL_android.c | 22 ++--
src/core/android/SDL_android.h | 4 +-
src/haptic/android/SDL_syshaptic.c | 3 +-
src/joystick/android/SDL_sysjoystick.c | 23 +---
7 files changed, 86 insertions(+), 88 deletions(-)
diff --git a/android-project/app/proguard-rules.pro b/android-project/app/proguard-rules.pro
index a13768966c3ea..975554b8a5426 100644
--- a/android-project/app/proguard-rules.pro
+++ b/android-project/app/proguard-rules.pro
@@ -69,9 +69,9 @@
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager {
void joystickSetSensorsEnabled(int, boolean);
- void pollInputDevices();
+ void detectDevices();
void joystickSetLED(int, int, int, int);
- void pollHapticDevices();
+ void detectHapticDevices();
void hapticRun(int, float, int);
void hapticRumble(int, float, float, int);
void hapticStop(int);
diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
index f382854023329..4edeecc33cf28 100644
--- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
+++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
@@ -465,6 +465,8 @@ public void onClick(DialogInterface dialog,int id) {
mSingleton = this;
SDL.setContext(this);
+ SDLControllerManager.initializeDeviceListener();
+
mClipboardHandler = new SDLClipboardHandler();
mHIDDeviceManager = HIDDeviceManager.acquire(this);
@@ -545,7 +547,7 @@ protected void onPause() {
if (mHIDDeviceManager != null) {
mHIDDeviceManager.setFrozen(true);
- }
+ }
if (!mHasMultiWindow) {
pauseNativeThread();
@@ -559,7 +561,7 @@ protected void onResume() {
if (mHIDDeviceManager != null) {
mHIDDeviceManager.setFrozen(false);
- }
+ }
if (!mHasMultiWindow) {
resumeNativeThread();
@@ -638,7 +640,7 @@ public void onWindowFocusChanged(boolean hasFocus) {
if (hasFocus || !SDLActivity.nativeGetHintBoolean("SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS", false)) {
if (mHIDDeviceManager != null) {
mHIDDeviceManager.setFrozen(!hasFocus);
- }
+ }
}
if (SDLActivity.mBrokenLibraries) {
diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java b/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java
index 8681d050b72b6..c86ba69c19700 100644
--- a/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java
+++ b/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java
@@ -6,6 +6,8 @@
import java.util.List;
import android.content.Context;
+import android.hardware.input.InputManager;
+import android.hardware.input.InputManager.InputDeviceListener;
import android.hardware.lights.Light;
import android.hardware.lights.LightsRequest;
import android.hardware.lights.LightsManager;
@@ -68,6 +70,12 @@ static void initialize() {
}
}
+ static void initializeDeviceListener() {
+ InputManager im = (InputManager) SDL.getContext().getSystemService(Context.INPUT_SERVICE);
+ im.registerInputDeviceListener(new SDLDeviceListener(), null);
+ }
+
+
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
static public boolean handleJoystickMotionEvent(MotionEvent event) {
return mJoystickHandler.handleMotionEvent(event);
@@ -76,8 +84,8 @@ static public boolean handleJoystickMotionEvent(MotionEvent event) {
/**
* This method is called by SDL using JNI.
*/
- static void pollInputDevices() {
- mJoystickHandler.pollInputDevices();
+ static void detectDevices() {
+ mJoystickHandler.detectDevices();
}
/**
@@ -97,8 +105,8 @@ static void joystickSetSensorsEnabled(int device_id, boolean enabled) {
/**
* This method is called by SDL using JNI.
*/
- static void pollHapticDevices() {
- mHapticHandler.pollHapticDevices();
+ static void detectHapticDevices() {
+ mHapticHandler.detectHapticDevices();
}
/**
@@ -151,7 +159,27 @@ static public boolean isDeviceSDLJoystick(int deviceId) {
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
);
}
+}
+
+
+class SDLDeviceListener implements InputDeviceListener
+{
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) {
+ SDLControllerManager.mJoystickHandler.deviceAdded(deviceId);
+ }
+ }
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ // InputDevice.getDevice(deviceId) returns NULL. so we cannot check device type
+ SDLControllerManager.mJoystickHandler.deviceRemoved(deviceId);
+ }
}
@@ -228,16 +256,21 @@ public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
/**
* Handles adding and removing of input devices.
*/
- synchronized void pollInputDevices() {
- int[] deviceIds = InputDevice.getDeviceIds();
-
- for (int device_id : deviceIds) {
- if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
- SDLJoystick joystick = getJoystick(device_id);
- if (joystick == null) {
- InputDevice joystickDevice = InputDevice.getDevice(device_id);
- joystick = new SDLJoystick();
- joystick.device_id = device_id;
+ synchronized void detectDevices() {
+ int[] deviceIds = InputDevice.getDeviceIds();
+ for (int device_id : deviceIds) {
+ if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
+ deviceAdded(device_id);
+ }
+ }
+ }
+
+ void deviceAdded(int device_id) {
+ SDLJoystick joystick = getJoystick(device_id);
+ if (joystick == null) {
+ InputDevice joystickDevice = InputDevice.getDevice(device_id);
+ joystick = new SDLJoystick();
+ joystick.device_id = device_id;
joystick.name = joystickDevice.getName();
joystick.desc = getJoystickDescriptor(joystickDevice);
joystick.axes = new ArrayList<InputDevice.MotionRange>();
@@ -299,44 +332,27 @@ synchronized void pollInputDevices() {
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, can_rumble, has_rgb_led,
has_accelerometer, has_gyroscope);
}
- }
- }
- /* Check removed devices */
- ArrayList<Integer> removedDevices = null;
- for (SDLJoystick joystick : mJoysticks) {
- int device_id = joystick.device_id;
- int i;
- for (i = 0; i < deviceIds.length; i++) {
- if (device_id == deviceIds[i]) break;
- }
- if (i == deviceIds.length) {
- if (removedDevices == null) {
- removedDevices = new ArrayList<Integer>();
- }
- removedDevices.add(device_id);
- }
- }
+ }
+
+ void deviceRemoved(int device_id) {
+ for (int i = 0; i < mJoysticks.size(); i++) {
+ if (mJoysticks.get(i).device_id == device_id) {
- if (removedDevices != null) {
- for (int device_id : removedDevices) {
SDLControllerManager.nativeRemoveJoystick(device_id);
- for (int i = 0; i < mJoysticks.size(); i++) {
- if (mJoysticks.get(i).device_id == device_id) {
- if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
- if (mJoysticks.get(i).lightsSession != null) {
- try {
- mJoysticks.get(i).lightsSession.close();
- } catch (Exception e) {
- // Session may already be unregistered when device disconnects
- }
- mJoysticks.get(i).lightsSession = null;
- }
+
+ if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
+ if (mJoysticks.get(i).lightsSession != null) {
+ try {
+ mJoysticks.get(i).lightsSession.close();
+ } catch (Exception e) {
+ // Session may already be unregistered when device disconnects
}
- mJoysticks.remove(i);
- break;
+ mJoysticks.get(i).lightsSession = null;
}
}
+ mJoysticks.remove(i);
+ break;
}
}
}
@@ -701,7 +717,7 @@ void stop(int device_id) {
}
}
- synchronized void pollHapticDevices() {
+ synchronized void detectHapticDevices() {
final int deviceId_VIBRATOR_SERVICE = 999999;
boolean hasVibratorService = false;
diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c
index c14f425d862ae..ba11cb44a21ff 100644
--- a/src/core/android/SDL_android.c
+++ b/src/core/android/SDL_android.c
@@ -407,10 +407,10 @@ static jmethodID midAudioSetThreadPriority;
static jclass mControllerManagerClass;
// method signatures
-static jmethodID midPollInputDevices;
+static jmethodID midDetectDevices;
static jmethodID midJoystickSetLED;
static jmethodID midJoystickSetSensorsEnabled;
-static jmethodID midPollHapticDevices;
+static jmethodID midDetectHapticDevices;
static jmethodID midHapticRun;
static jmethodID midHapticRumble;
static jmethodID midHapticStop;
@@ -752,14 +752,14 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env
mControllerManagerClass = (jclass)((*env)->NewGlobalRef(env, cls));
- midPollInputDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass,
- "pollInputDevices", "()V");
+ midDetectDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass,
+ "detectDevices", "()V");
midJoystickSetLED = (*env)->GetStaticMethodID(env, mControllerManagerClass,
"joystickSetLED", "(IIII)V");
midJoystickSetSensorsEnabled = (*env)->GetStaticMethodID(env, mControllerManagerClass,
"joystickSetSensorsEnabled", "(IZ)V");
- midPollHapticDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass,
- "pollHapticDevices", "()V");
+ midDetectHapticDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass,
+ "detectHapticDevices", "()V");
midHapticRun = (*env)->GetStaticMethodID(env, mControllerManagerClass,
"hapticRun", "(IFI)V");
midHapticRumble = (*env)->GetStaticMethodID(env, mControllerManagerClass,
@@ -767,7 +767,7 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env
midHapticStop = (*env)->GetStaticMethodID(env, mControllerManagerClass,
"hapticStop", "(I)V");
- if (!midPollInputDevices || !midJoystickSetLED || !midJoystickSetSensorsEnabled || !midPollHapticDevices || !midHapticRun || !midHapticRumble || !midHapticStop) {
+ if (!midDetectDevices || !midJoystickSetLED || !midJoystickSetSensorsEnabled || !midDetectHapticDevices || !midHapticRun || !midHapticRumble || !midHapticStop) {
__android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?");
}
@@ -2639,10 +2639,10 @@ void Android_JNI_InitTouch(void)
(*env)->CallStaticVoidMethod(env, mActivityClass, midInitTouch);
}
-void Android_JNI_PollInputDevices(void)
+void Android_JNI_DetectDevices(void)
{
JNIEnv *env = Android_JNI_GetEnv();
- (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollInputDevices);
+ (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midDetectDevices);
}
void Android_JNI_JoystickSetLED(int device_id, int red, int green, int blue)
@@ -2657,10 +2657,10 @@ void Android_JNI_JoystickSetSensorsEnabled(int device_id, bool enabled)
(*env)->CallStaticVoidMethod(env, mControllerManagerClass, midJoystickSetSensorsEnabled, device_id, (enabled == 1));
}
-void Android_JNI_PollHapticDevices(void)
+void Android_JNI_DetectHapticDevices(void)
{
JNIEnv *env = Android_JNI_GetEnv();
- (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollHapticDevices);
+ (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midDetectHapticDevices);
}
void Android_JNI_HapticRun(int device_id, float intensity, int length)
diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h
index a1bc1c7c31a7c..fd16927595953 100644
--- a/src/core/android/SDL_android.h
+++ b/src/core/android/SDL_android.h
@@ -102,12 +102,12 @@ bool Android_JNI_HasClipboardText(void);
int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent);
// Joystick support
-void Android_JNI_PollInputDevices(void);
+void Android_JNI_DetectDevices(void);
void Android_JNI_JoystickSetLED(int device_id, int red, int green, int blue);
void Android_JNI_JoystickSetSensorsEnabled(int device_id, bool enabled);
// Haptic support
-void Android_JNI_PollHapticDevices(void);
+void Android_JNI_DetectHapticDevices(void);
void Android_JNI_HapticRun(int device_id, float intensity, int length);
void Android_JNI_HapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length);
void Android_JNI_HapticStop(int device_id);
diff --git a/src/haptic/android/SDL_syshaptic.c b/src/haptic/android/SDL_syshaptic.c
index 760854e9f164a..da36eb9595dae 100644
--- a/src/haptic/android/SDL_syshaptic.c
+++ b/src/haptic/android/SDL_syshaptic.c
@@ -41,8 +41,7 @@ static int numhaptics = 0;
bool SDL_SYS_HapticInit(void)
{
- Android_JNI_PollHapticDevices();
-
+ Android_JNI_DetectHapticDevices();
return true;
}
diff --git a/src/joystick/android/SDL_sysjoystick.c b/src/joystick/android/SDL_sysjoystick.c
index 16992e8255fb2..fc3368ed780d2 100644
--- a/src/joystick/android/SDL_sysjoystick.c
+++ b/src/joystick/android/SDL_sysjoystick.c
@@ -535,11 +535,9 @@ void Android_RemoveJoystick(int device_id)
SDL_UnlockJoysticks();
}
-static void ANDROID_JoystickDetect(void);
-
static bool ANDROID_JoystickInit(void)
{
- ANDROID_JoystickDetect();
+ Android_JNI_DetectDevices();
return true;
}
@@ -550,16 +548,9 @@ static int ANDROID_JoystickGetCount(void)
static void ANDROID_JoystickDetect(void)
{
- /* Support for device connect/disconnect is API >= 16 only,
- * so we poll every three seconds
+ /* Support for device connect/disconnect is implemented using InputDeviceListener
* Ref: http://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html
*/
- static Uint64 timeout = 0;
- Uint64 now = SDL_GetTicks();
- if (!timeout || now >= timeout) {
- timeout = now + 3000;
- Android_JNI_PollInputDevices();
- }
}
static bool ANDROID_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
@@ -596,16 +587,6 @@ static SDL_joylist_item *JoystickByDeviceId(int device_id)
item = item->next;
}
- // Joystick not found, try adding it
- ANDROID_JoystickDetect();
-
- while (item) {
- if (item->device_id == device_id) {
- return item;
- }
- item = item->next;
- }
-
return NULL;
}