Skip to content
Browse files

Fixed bug 3426 - Fixes for joystick related issues


This report is going to cover three issues, with a suggestion for fixes. For reference, tests were done using an installation of android-x86-5.1-rc1.iso within a VirtualBox session. I've actually used an adapter that accepts up to two Playstation 1/2 controllers.

A ZIP file should be attached, with the following contents:
- The patch file itself.
- Outputs of joysticks lists from testjoystick with different orders (before fixing bug).
- Game controllers database entries (for reference).
--- Different outputs for different platforms may stem from different tools being used; The Android mapping was manually constructed using a previously available mapping as a base.
--- Note that it turns out the Linux mapping is already out there in some form:

And so, let's begin listing the issues:

1. While changeset has good intentions, it appears to make various input devices being mistakenly detected as SDL joysticks. I got lists of the devices from joysticktest, given in the ZIP file. "badordering.txt" is what I get if the device has been plugged since a reboot of the virtual machine, while I've gotten "goodordering.txt" after hot-plugging the USB adapter. As expected, only in the latter case I could use the controller in the test program (assuming it isn't modified).

To take care of this, I updated pollInputDevices and added the function SDLActivity.isDeviceSDLJoystick, in order to have a better filter. Note that it also checks that the device id is non-negative, since VIRTUAL_KEYBOARD appears to include a SOURCE_DPAD, and I should probably keep accepting it as an SDL_joystick (good if you want, say, to support multiple independent d-pad devices).

I hope the device id filter does not break support for the virtual remote (mentioned in the changeset above).

2. So there's a weird glitch here, where the game controller is reported to have SOURCE_KEYBOARD and SOURCE_JOYSTICK, while each controller button press/release emits a KeyEvent with SOURCE_KEYBOARD only. So obviously any test going over the event's own sources is expected to fail.

It is possible to try and filter by the key code, but then there are the dpad key codes, which can also be emitted by actual PC keyboard's key presses/releases (the arrow keys).

So instead, I just call the newly added isDeviceSDLJoystick function again and check if the input device (not the event) has any source considered to be a joystick/gamepad for us.

3. Finally, if SDL2 properly detects an SDL_Joystick being connected, but it is not opened, then whenever a KeyEvent is received after a button press/release from the same controller, SDLActivity.onNativePadDown/onNativePadUp returns a negative value. In such a case, the onKey handler continues to check for SOURCE_KEYBOARD (and possibly also SOURCE_MOUSE), which is clearly not desired.

And so, in the given patch, the return values of onNativePadDown and onNativePadUp are ignored and "true" is returned either way.

(Note: Maybe the native functions should be modified to have the return value of "void".)

Finally, as another side-note, I've noticed that the various bitwise tests for sources are wrong. For instance, to check if an InputDevice 'device' has source SOURCE_JOYSTICK, the value (device.getSources() & SOURCE_JOYSTICK) should be compared to SOURCE_JOYSTICK, not 0.
However, I think there's enough that this patch covers. At the least, isDeviceSDLJoystick partially solves this.
  • Loading branch information
slouken committed Sep 29, 2016
1 parent f369d16 commit 257c89727abc6ca5f5443fe78ca476df2ee1f19a
Showing with 32 additions and 21 deletions.
  1. +32 −21 android-project/src/org/libsdl/app/
@@ -724,6 +724,21 @@ public static void pollInputDevices() {

// Check if a given device is considered a possible SDL joystick
public static boolean isDeviceSDLJoystick(int deviceId) {
InputDevice device = InputDevice.getDevice(deviceId);
// We cannot use InputDevice.isVirtual before API 16, so let's accept
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
if ((device == null) || (deviceId < 0)) {
return false;
int sources = device.getSources();
return (((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) ||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)

// APK expansion files support

/** object or null. */
@@ -1220,23 +1235,25 @@ public void run(){
public boolean onKey(View v, int keyCode, KeyEvent event) {
// Dispatch the different events depending on where they come from
// So, we try to process them as DPAD or GAMEPAD events first, if that fails we try them as KEYBOARD

if ( (event.getSource() & InputDevice.SOURCE_GAMEPAD) != 0 ||
(event.getSource() & InputDevice.SOURCE_DPAD) != 0 ) {
// So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD
// Furthermore, it's possible a game controller has SOURCE_KEYBOARD and
// SOURCE_JOYSTICK, while its key events arrive from the keyboard source
// So, retrieve the device itself and check all of its sources
if (SDLActivity.isDeviceSDLJoystick(event.getDeviceId())) {
// Note that we always process a pressed/released button here, as an unopened
// SDL_Joystick's button press should not be processed as a keyboard's key press
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (SDLActivity.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
return true;
SDLActivity.onNativePadDown(event.getDeviceId(), keyCode);
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
if (SDLActivity.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
return true;
SDLActivity.onNativePadUp(event.getDeviceId(), keyCode);
return true;

if( (event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
if ((event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
//Log.v("SDL", "key down: " + keyCode);
@@ -1586,13 +1603,7 @@ public void pollInputDevices() {
if (joystick == null) {
joystick = new SDLJoystick();
InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);

if (
(joystickDevice.getSources() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0
(joystickDevice.getSources() & InputDevice.SOURCE_CLASS_BUTTON) != 0
if (SDLActivity.isDeviceSDLJoystick(deviceIds[i])) {
joystick.device_id = deviceIds[i]; = joystickDevice.getName();
joystick.axes = new ArrayList<InputDevice.MotionRange>();
@@ -1601,7 +1612,7 @@ public void pollInputDevices() {
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
Collections.sort(ranges, new RangeComparator());
for (InputDevice.MotionRange range : ranges ) {
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) {
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
if (range.getAxis() == MotionEvent.AXIS_HAT_X ||
range.getAxis() == MotionEvent.AXIS_HAT_Y) {
@@ -1655,7 +1666,7 @@ protected SDLJoystick getJoystick(int device_id) {

public boolean handleMotionEvent(MotionEvent event) {
if ( (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
int actionPointerIndex = event.getActionIndex();
int action = event.getActionMasked();
switch(action) {

0 comments on commit 257c897

Please sign in to comment.