From acc06bce355097b0df4e4a5f0955928dbb298bb7 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 8 Jun 2026 14:19:43 -0700
Subject: [PATCH] android: fixed crash adding joysticks before joysticks are
initialized
Fixes https://github.com/libsdl-org/SDL/issues/15777
---
.../org/libsdl/app/SDLControllerManager.java | 139 ++++++++++--------
src/joystick/android/SDL_sysjoystick.c | 12 +-
2 files changed, 81 insertions(+), 70 deletions(-)
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 f61bab1d54db7..0e6c20494584b 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
@@ -275,82 +275,91 @@ public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
* Handles adding and removing of input devices.
*/
synchronized void detectDevices() {
- int[] deviceIds = InputDevice.getDeviceIds();
- for (int device_id : deviceIds) {
- if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
- deviceAdded(device_id);
- }
- }
- }
+ int[] deviceIds = InputDevice.getDeviceIds();
+ for (int device_id : deviceIds) {
+ if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
+ deviceAdded(device_id);
+ }
+ }
+ }
void deviceAdded(int device_id) {
+ InputDevice joystickDevice = InputDevice.getDevice(device_id);
+ if (joystickDevice == null ) {
+ return;
+ }
+
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>();
- joystick.hats = new ArrayList<InputDevice.MotionRange>();
- java.util.Set<Integer> axisStrsSet = new java.util.HashSet<Integer>();
- joystick.lights = new ArrayList<Light>();
-
- List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
- Collections.sort(ranges, new RangeComparator());
- for (InputDevice.MotionRange range : ranges) {
- if (((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) && axisStrsSet.add(range.getAxis())) {
- if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
- joystick.hats.add(range);
- } else {
- joystick.axes.add(range);
- }
- }
+ joystick.name = joystickDevice.getName();
+ joystick.desc = getJoystickDescriptor(joystickDevice);
+ joystick.axes = new ArrayList<InputDevice.MotionRange>();
+ joystick.hats = new ArrayList<InputDevice.MotionRange>();
+ java.util.Set<Integer> axisStrsSet = new java.util.HashSet<Integer>();
+ joystick.lights = new ArrayList<Light>();
+
+ List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
+ Collections.sort(ranges, new RangeComparator());
+ for (InputDevice.MotionRange range : ranges) {
+ if (((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) && axisStrsSet.add(range.getAxis())) {
+ if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
+ joystick.hats.add(range);
+ } else {
+ joystick.axes.add(range);
}
+ }
+ }
- boolean can_rumble = false;
- boolean has_rgb_led = false;
- boolean has_accelerometer = false;
- boolean has_gyroscope = false;
- if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
- VibratorManager vibratorManager = joystickDevice.getVibratorManager();
- int[] vibrators = vibratorManager.getVibratorIds();
- if (vibrators.length > 0) {
- can_rumble = true;
- }
- LightsManager lightsManager = joystickDevice.getLightsManager();
- List<Light> lights = lightsManager.getLights();
- for (Light light : lights) {
- if (light.hasRgbControl()) {
- joystick.lights.add(light);
- }
- }
- if (!joystick.lights.isEmpty()) {
- joystick.lightsSession = lightsManager.openSession();
- has_rgb_led = true;
- }
- SensorManager sensorManager = joystickDevice.getSensorManager();
- if (sensorManager != null) {
- joystick.sensorManager = sensorManager;
- joystick.sensorListener = new SDLJoySensorListener(joystick.device_id);
- joystick.accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
- if (joystick.accelerometerSensor != null) {
- has_accelerometer = true;
- }
- joystick.gyroscopeSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
- if (joystick.gyroscopeSensor != null) {
- has_gyroscope = true;
- }
- }
+ if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
+ LightsManager lightsManager = joystickDevice.getLightsManager();
+ List<Light> lights = lightsManager.getLights();
+ for (Light light : lights) {
+ if (light.hasRgbControl()) {
+ joystick.lights.add(light);
}
-
- mJoysticks.add(joystick);
- SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
- getVendorId(joystickDevice), getProductId(joystickDevice),
- getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, can_rumble, has_rgb_led,
- has_accelerometer, has_gyroscope);
}
+ if (!joystick.lights.isEmpty()) {
+ joystick.lightsSession = lightsManager.openSession();
+ }
+ SensorManager sensorManager = joystickDevice.getSensorManager();
+ if (sensorManager != null) {
+ joystick.sensorManager = sensorManager;
+ joystick.sensorListener = new SDLJoySensorListener(joystick.device_id);
+ joystick.accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ joystick.gyroscopeSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
+ }
+ }
+
+ mJoysticks.add(joystick);
+ }
+
+ boolean can_rumble = false;
+ boolean has_rgb_led = false;
+ boolean has_accelerometer = false;
+ boolean has_gyroscope = false;
+ if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
+ VibratorManager vibratorManager = joystickDevice.getVibratorManager();
+ int[] vibrators = vibratorManager.getVibratorIds();
+ if (vibrators.length > 0) {
+ can_rumble = true;
+ }
+ if (!joystick.lights.isEmpty()) {
+ has_rgb_led = true;
+ }
+ if (joystick.accelerometerSensor != null) {
+ has_accelerometer = true;
+ }
+ if (joystick.gyroscopeSensor != null) {
+ has_gyroscope = true;
+ }
+ }
+ SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
+ getVendorId(joystickDevice), getProductId(joystickDevice),
+ getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, can_rumble, has_rgb_led,
+ has_accelerometer, has_gyroscope);
}
void deviceRemoved(int device_id) {
diff --git a/src/joystick/android/SDL_sysjoystick.c b/src/joystick/android/SDL_sysjoystick.c
index fc3368ed780d2..a3f105612fe1e 100644
--- a/src/joystick/android/SDL_sysjoystick.c
+++ b/src/joystick/android/SDL_sysjoystick.c
@@ -389,6 +389,13 @@ void Android_AddJoystick(int device_id, const char *name, const char *desc, int
SDL_GUID guid;
int i;
+ // Java might notify us about joysticks being added before joysticks have
+ // been initialized. That's fine, we'll get called again with the full set
+ // in Android_JNI_DetectDevices()
+ if (!SDL_JoysticksInitialized()) {
+ return;
+ }
+
SDL_LockJoysticks();
if (!SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, true)) {
@@ -726,10 +733,6 @@ static void ANDROID_JoystickClose(SDL_Joystick *joystick)
static void ANDROID_JoystickQuit(void)
{
-/* We don't have any way to scan for joysticks at init, so don't wipe the list
- * of joysticks here in case this is a reinit.
- */
-#if 0
SDL_joylist_item *item = NULL;
SDL_joylist_item *next = NULL;
@@ -742,7 +745,6 @@ static void ANDROID_JoystickQuit(void)
SDL_joylist = SDL_joylist_tail = NULL;
numjoysticks = 0;
-#endif // 0
}
static bool ANDROID_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)