Fix crashes and ANRs in controller LED handling

This commit is contained in:
Cameron Gutman
2026-09-01 21:15:51 -05:00
parent ad8614907c
commit abde6021e8
@@ -786,6 +786,13 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
break;
}
}
// Light.hasRgbControl() was totally broken prior to Android 14.
// It always returned true because LIGHT_CAPABILITY_RGB was defined as 0,
// so we will just guess RGB is supported if it's a PlayStation controller.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE && context.vendorId != 0x054c) {
context.hasRgbLed = false;
}
}
// Detect if the gamepad has Mode and Select buttons according to the Android key layouts.
@@ -2305,25 +2312,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// Ignore input devices without an RGB LED
if (deviceContext.controllerNumber == controllerNumber && deviceContext.hasRgbLed) {
// Create a new light session if one doesn't already exist
if (deviceContext.lightsSession == null) {
deviceContext.lightsSession = deviceContext.inputDevice.getLightsManager().openSession();
}
// Convert the RGB components into the integer value that LightState uses
int argbValue = 0xFF000000 | ((r << 16) & 0xFF0000) | ((g << 8) & 0xFF00) | (b & 0xFF);
LightState lightState = new LightState.Builder().setColor(argbValue).build();
deviceContext.ledArgbValue = 0xFF000000 | ((r << 16) & 0xFF0000) | ((g << 8) & 0xFF00) | (b & 0xFF);
// Set the RGB value for each RGB-controllable LED on the device
LightsRequest.Builder lightsRequestBuilder = new LightsRequest.Builder();
for (Light light : deviceContext.inputDevice.getLightsManager().getLights()) {
if (light.hasRgbControl()) {
lightsRequestBuilder.addLight(light, lightState);
}
}
// Apply the LED changes
deviceContext.lightsSession.requestLights(lightsRequestBuilder.build());
backgroundThreadHandler.removeCallbacks(deviceContext.setLedStateRunnable);
backgroundThreadHandler.post(deviceContext.setLedStateRunnable);
}
}
}
@@ -2958,6 +2951,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
public boolean hasRgbLed;
public LightsManager.LightsSession lightsSession;
public int ledArgbValue;
// These are BatteryState values, not Moonlight values
public int lastReportedBatteryStatus;
@@ -3034,6 +3028,34 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
};
public final Runnable setLedStateRunnable = new Runnable() {
@TargetApi(Build.VERSION_CODES.S)
@Override
public void run() {
LightState lightState = new LightState.Builder().setColor(ledArgbValue).build();
// Set the RGB value for each RGB-controllable LED on the device
boolean hasRgbLight = false;
LightsRequest.Builder lightsRequestBuilder = new LightsRequest.Builder();
for (Light light : inputDevice.getLightsManager().getLights()) {
if (light.hasRgbControl()) {
lightsRequestBuilder.addLight(light, lightState);
hasRgbLight = true;
}
}
if (hasRgbLight) {
// Create a new light session if one doesn't already exist
if (lightsSession == null) {
lightsSession = inputDevice.getLightsManager().openSession();
}
// Apply the LED changes
lightsSession.requestLights(lightsRequestBuilder.build());
}
}
};
@Override
public void destroy() {
super.destroy();
@@ -3046,6 +3068,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
}
backgroundThreadHandler.removeCallbacks(enableSensorRunnable);
backgroundThreadHandler.removeCallbacks(setLedStateRunnable);
if (gyroListener != null) {
sensorManager.unregisterListener(gyroListener);
@@ -3056,7 +3079,11 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (lightsSession != null) {
lightsSession.close();
try {
lightsSession.close();
} catch (RuntimeException e) {
LimeLog.warning("Error closing LightsSession: " + e.getMessage());
}
}
}
@@ -3128,10 +3155,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
capabilities |= MoonBridge.LI_CCAP_BATTERY_STATE;
}
// Light.hasRgbControl() was totally broken prior to Android 14.
// It always returned true because LIGHT_CAPABILITY_RGB was defined as 0,
// so we will just guess RGB is supported if it's a PlayStation controller.
if (hasRgbLed && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE || type == MoonBridge.LI_CTYPE_PS)) {
if (hasRgbLed) {
capabilities |= MoonBridge.LI_CCAP_RGB_LED;
}
}
@@ -3195,6 +3219,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
this.lightsSession = oldContext.lightsSession;
oldContext.lightsSession = null;
}
this.ledArgbValue = oldContext.ledArgbValue;
this.gyroReportRateHz = oldContext.gyroReportRateHz;
this.accelReportRateHz = oldContext.accelReportRateHz;
@@ -3219,6 +3244,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
// Re-enable sensors on the new context
enableSensors();
// Refresh the LED state if it's set. We don't know if there was a pending LED state
// callback when the context was migrated, so we'll reset it just to be safe.
if (this.lightsSession != null) {
backgroundThreadHandler.post(setLedStateRunnable);
}
// Refresh battery state and start the battery state polling again
backgroundThreadHandler.post(batteryStateUpdateRunnable);
}