Processing Input Events

Download PDF

Processing Input Events

Android automatically provides a basic level of support for GamePads for many applications, however dedicated code is required to properly support all GamePad features. This technical note details how to write controller-aware software for Android and the PlayJam platform.

 

Compatible ControllersCompatible Controllers

The PlayJam Platform supports standard HID controllers, which are processed using Android standard mechanisms. The available inputs are as follows:

Name

KeyCode (KeyEvent.*) or Motion Axis (MotionEvent.*)

Fallback

Left Thumbstick

Right Thumbstick

D-Pad



Left Thumb Button
Right Thumb Button
Left Shoulder 1
Right Shoulder 1
A Button
B Button
X Button
Y Button
Start Button
Back Button
Home Button

MotionEvent.AXIS_X
MotionEvent.AXIS_Y
MotionEvent.AXIS_Z
MotionEvent.AXIS_RZ
MotionEvent.AXIS_HAT_X
MotionEvent.AXIS_HAT_Y


KEYCODE_BUTTON_THUMBL
KEYCODE_BUTTON_THUMBR
KEYCODE_BUTTON_L1
KEYCODE_BUTTON_R1
KEYCODE_BUTTON_A
KEYCODE_BUTTON_B
KEYCODE_BUTTON_X
KEYCODE_BUTTON_Y
KEYCODE_BUTTON_START
KEYCODE_BACK
KEYCODE_HOME

-

-

KEYCODE_DPAD_LEFT
KEYCODE_DPAD_RIGHT
KEYCODE_DPAD_UP
KEYCODE_DPAD_DOWN
-
-
-
-
KEYCODE_DPAD_CENTER
KEYCODE_BACK
KEYCODE_DPAD_CENTER
KEYCODE_BACK
-
-
N/A

 

Event SourcesEvent Sources

Note that the source identified returned by an event (via MotionEvent.getSource() or equivalent returns the source identifier for the device – which generally corresponds with the full set of capabilities of the device. As such, all events regardless of type will report as tge source type. IE – D-Pas keyboard event, Button Presses and Analog Motion will all report the same type – such as SOURCE_JOYSTICK.

 

Input Processing on AndroidInput Processing on Android

Android offer input events to applications in several passes. Analog dpad events may be rewritten as key events. Key events may be rewritten as fallback events if they’re not processes by the application. Navigation events may be intercepted by the application but failing that they are processed by Android – this is how Android provides basic navigational support for GamePads.

Note hat Home key events cannot be intercepted – they are processed directly by Android for launching the ‘Home’ app.

Multiple ControllersMultiple Controllers

Android Bugs Android Bugs

Due to an overly simplistic implementation of the dpad translation code in Android, (up to and including Android 4.4), using onKeyDown() and onKeyUp() events with multiple controllers is not recommended. While onKey*() events work acceptably for single controllers, when multiple controllers are used it is possible for events from one controller to be misidentified as coming from another. Details of this bug are included in Appendix A.

Identifying ControllersIdentifying Controllers

Android provides several different methods for identifying controllers, however they are not all equal in terms of functionality:

  • InputDevice.getId()
    • Guaranteed unique for the current power cycle
    • Not stable – They are simple incrementing integers that are assigned to each device as it connects – even if it is a device that has already been connected – as such it is guaranteed that a different value will be returned for a controller before and after a power cycle of that controller, (i.e. if it disconnects, its getId() will change).
  • InputDevice.getDescriptor()
    • Stable – The value for any controllers will stay the same through power cycles of the controller and/or the platform – the value itself is derived from physical properties of the device.
    • Not guaranteed unique – As the value is derived from a set of physical parameters of the device, it is possible for devices which share the same set of parameters to return the same value. This is generally not a problem for Bluetooth devices, as the MAC address of the Bluetooth client is part of the input to the id generator.
  • InputDevice.getName()
    • PlayJam provides managed naming of controllers – in that the controller with a specific player lit up will remain the same name – so the controller with light one on, (Player 1), will always return the device name of “GameStick Controller 1”. Note that the order of light assignment is currently related to the power up order of controllers – so turning off controllers and repowering in a different order will result in name changes – however the controller itself will still show the correct light for its name, (making the assignment clean to the user).

Example CodeExample Code

private static final float DPAD_DEAD_ZONE = 0.5f;
private SparseIntArray mDpadXState = new SparseIntArray();
private SparseIntArray mDpadYState = new SparseIntArray();

@Override

public boolean onGenericMotionEvent(MotionEvent event) {
int padId = event.getDevice().getId();

{
float x = event.getAxisValue(MotionEvent.AXIS_X);
float y = event.getAxisValue(MotionEvent.AXIS_Y); onLeftStickEvent(padId, (int) (x * 127), (int) (y * 127));

} {

} {

float x = event.getAxisValue(MotionEvent.AXIS_Z);
float y = event.getAxisValue(MotionEvent.AXIS_RZ); onRightStickEvent(padId, ( int) (x * 127), (int) (y * 127));

float x = event.getAxisValue(MotionEvent.AXIS_HAT_X);
float y = event.getAxisValue(MotionEvent.AXIS_HAT_Y);

int intX = (int) ((Math.abs(x)< DPAD_DEAD_ZONE)? 0 : Math.signum(x));
int intY = (int) ((Math.abs(y)< DPAD_DEAD_ZONE) ? 0 : Math.signum(y));

int dpadXState = mDpadXState.get(padId);
if (dpadXState != intX) {

// Send up events for keys that were down
if (dpadXState > 0) {
doKeyEvent(event, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_RIGHT);
} else if (dpadXState < 0) {
doKeyEvent(event, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_LEFT);
}

// Send down events for the key that's pressed
if (intX > 0) {
doKeyEvent(event, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT);
} else if (intX < 0){
doKeyEvent(event, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT);
}

mDpadXState.put(padId, intX);

}

int dpadYState = mDpadYState.get(padId); if (dpadYState != intY) {

// Send up events for keys that were down

if (dpadYState > 0) {
doKeyEvent(event, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_DOWN);
} else if (dpadYState < 0) {
doKeyEvent(event, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_UP);
}

// Send down events for the key that's pressed
if (intY > 0) {
doKeyEvent(event, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN);
} else if (intY < 0){
doKeyEvent(event, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP);
}

mDpadYState.put(padId, intY);

}

}

return true ; }

public void doKeyEvent(MotionEvent event, int action, int keycode) {
final long time = event.getEventTime();
final int metaState = event.getMetaState();
final int deviceId = event.getDeviceId();
final int source = event.getSource();

if (action == KeyEvent.ACTION_DOWN) {
this.onKeyDown(keycode, new KeyEvent(time, time, KeyEvent.ACTION_DOWN, keycode, 0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
} else {
this .onKeyUp(keycode, new KeyEvent(time, time, KeyEvent.ACTION_UP, keycode, 0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
} }

public void onLeftStickEvent(int padId, int x, int y) {
//TODO: Process left thumbstick here x and y are in the range -127,+127 (Down and Right are positive)
}

public void onRightStickEvent(int padId, int x, int y){
//TODO: Process right thumbstick here x and y are in the range -127,+127 (Down and Right are positive)
}

 

APPENDIX A – ANDROID MULTIPLE D-PAD BUGAPPENDIX A – ANDROID MULTIPLE D-PAD BUG

ViewRootImpl.java/deliverGenericMotionEvent(...)

3337
3338// Deliver the event to the view.
3339if (mView.dispatchGenericMotionEvent(event)) { <---- If the developer handles onGenericMotion(), this returns true, and the event is absorbed.
updateJoystickDirection() is still called, but as the second parameter is false - no keyboard events are synthesized.

3340if (isJoystick) {
3341updateJoystickDirection(event, false);
3342}
3343finishInputEvent(q, true);
3344return;
3345}
3346
3347if (isJoystick) {
3348// Translate the joystick event into DPAD keys and try to deliver those.
3349updateJoystickDirection(event, true); <---- If the developer does not handle onGenericMotion(), this event updateJoystickDirection() is called
with true, and keyboard events are synthesized.

3350finishInputEvent(q, true);
3351} else {
3352finishInputEvent(q, false);
3353}
3354}
3355

 

ViewRootImpl.java/updateJoystickDirection(...)

3356private void updateJoystickDirection(MotionEvent event, boolean synthesizeNewKeys) {
3357final long time = event.getEventTime();
3358final int metaState = event.getMetaState();
3359final int deviceId = event.getDeviceId();
3360final int source = event.getSource();
3361
3362int xDirection = joystickAxisValueToDirection(event.getAxisValue(MotionEvent.AXIS_HAT_X));
3363if (xDirection == 0) {
3364xDirection = joystickAxisValueToDirection(event.getX());
3365}
3366
3367int yDirection = joystickAxisValueToDirection(event.getAxisValue(MotionEvent.AXIS_HAT_Y));
3368if (yDirection == 0) {
3369yDirection = joystickAxisValueToDirection(event.getY());
3370}
3371
3372if (xDirection != mLastJoystickXDirection) { <---- Note that this function does not take into account the source of the last event before using it for filtering. This is the cause of the bug.