Skip to content

Commit

Permalink
ANDROID: A tentative handling of handling joystick control
Browse files Browse the repository at this point in the history
Basically as a virtual mouse
  • Loading branch information
antoniou79 committed Oct 24, 2020
1 parent 9045bce commit 64eb1c0
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 12 deletions.
18 changes: 18 additions & 0 deletions backends/platform/android/android.cpp
Expand Up @@ -499,6 +499,24 @@ bool OSystem_Android::getFeatureState(Feature f) {
}
}

// TODO Re-eval if we need this here
Common::HardwareInputSet *OSystem_Android::getHardwareInputSet() {
using namespace Common;

CompositeHardwareInputSet *inputSet = new CompositeHardwareInputSet();
inputSet->addHardwareInputSet(new MouseHardwareInputSet(defaultMouseButtons));
inputSet->addHardwareInputSet(new KeyboardHardwareInputSet(defaultKeys, defaultModifiers));
inputSet->addHardwareInputSet(new JoystickHardwareInputSet(defaultJoystickButtons, defaultJoystickAxes));

return inputSet;
}

// TODO Re-eval if we need this here
Common::KeymapArray OSystem_Android::getGlobalKeymaps() {
Common::KeymapArray globalMaps = BaseBackend::getGlobalKeymaps();
return globalMaps;
}

Common::KeymapperDefaultBindings *OSystem_Android::getKeymapperDefaultBindings() {
Common::KeymapperDefaultBindings *keymapperDefaultBindings = new Common::KeymapperDefaultBindings();

Expand Down
2 changes: 2 additions & 0 deletions backends/platform/android/android.h
Expand Up @@ -115,6 +115,8 @@ class OSystem_Android : public ModularMutexBackend, public ModularGraphicsBacken

public:
virtual bool pollEvent(Common::Event &event) override;
virtual Common::HardwareInputSet *getHardwareInputSet() override;
virtual Common::KeymapArray getGlobalKeymaps() override;
virtual Common::KeymapperDefaultBindings *getKeymapperDefaultBindings() override;

virtual uint32 getMillis(bool skipRecord = false) override;
Expand Down
45 changes: 44 additions & 1 deletion backends/platform/android/events.cpp
Expand Up @@ -902,22 +902,65 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
break;

case JE_JOYSTICK:
e.mouse = dynamic_cast<AndroidGraphicsManager *>(_graphicsManager)->getMousePosition();

switch (arg1) {
// AMOTION_EVENT_ACTION_MOVE is 2 in NDK (https://developer.android.com/ndk/reference/group/input)
case AMOTION_EVENT_ACTION_MOVE:
e.mouse = dynamic_cast<AndroidGraphicsManager *>(_graphicsManager)->getMousePosition();
e.type = Common::EVENT_MOUSEMOVE;

// already multiplied by 100
e.mouse.x += arg2 * _joystick_scale / _eventScaleX;
e.mouse.y += arg3 * _joystick_scale / _eventScaleY;

break;
case AKEY_EVENT_ACTION_DOWN:
e.type = Common::EVENT_KEYDOWN;
break;
case AKEY_EVENT_ACTION_UP:
e.type = Common::EVENT_KEYUP;
break;
default:
LOGE("unhandled jaction on joystick: %d", arg1);
return;
}

if (arg1 != AMOTION_EVENT_ACTION_MOVE) {
switch (arg2) {
case AKEYCODE_BUTTON_1:
case AKEYCODE_BUTTON_2:
switch (arg1) {
case AKEY_EVENT_ACTION_DOWN:
e.type = (arg2 == AKEYCODE_BUTTON_1?
Common::EVENT_LBUTTONDOWN :
Common::EVENT_RBUTTONDOWN);
break;
case AKEY_EVENT_ACTION_UP:
e.type = (arg2 == AKEYCODE_BUTTON_1?
Common::EVENT_LBUTTONUP :
Common::EVENT_RBUTTONUP);
break;
}

e.mouse = dynamic_cast<AndroidGraphicsManager *>(_graphicsManager)->getMousePosition();

break;

case AKEYCODE_BUTTON_3:
e.kbd.keycode = Common::KEYCODE_ESCAPE;
e.kbd.ascii = Common::ASCII_ESCAPE;
break;

case AKEYCODE_BUTTON_4:
e.type = Common::EVENT_MAINMENU;
break;

default:
LOGW("unmapped gamepad key: %d", arg2);
return;
}
}

pushEvent(e);

return;
Expand Down
Expand Up @@ -404,6 +404,7 @@ public void onRelease(int key) {

public void onText(CharSequence p1) {}

// TODO - "Swipe" behavior does not seem to work currently. Should we support it?
public void swipeLeft() {
//Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - swipeLeft");
}
Expand Down Expand Up @@ -451,7 +452,7 @@ public void onKey(int key, int[] keysAround) {
_screenKeyboard = builtinKeyboard;
// TODO better to have specific dimensions in dp and not adjusted to parent
// it may resolve the issue of resizing the keyboard wrongly (smaller) when returning to the suspended Activity in low resolution
FrameLayout.LayoutParams sKeyboardLayout = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL | Gravity.FILL_HORIZONTAL);
FrameLayout.LayoutParams sKeyboardLayout = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);

_videoLayout.addView(_screenKeyboard, sKeyboardLayout);
_videoLayout.bringChildToFront(_screenKeyboard);
Expand Down
Expand Up @@ -4,7 +4,6 @@
import android.os.Message;
import android.content.Context;
//import android.util.Log;
import android.util.Log;
import android.view.KeyEvent;
import android.view.KeyCharacterMap;
import android.view.MotionEvent;
Expand Down Expand Up @@ -328,6 +327,13 @@ final public boolean onKey(View v, int keyCode, KeyEvent e) {
case KeyEvent.KEYCODE_BUTTON_MODE:
type = JE_GAMEPAD;
break;
case KeyEvent.KEYCODE_BUTTON_1:
case KeyEvent.KEYCODE_BUTTON_2:
case KeyEvent.KEYCODE_BUTTON_3:
case KeyEvent.KEYCODE_BUTTON_4:
// These are oddly detected with SOURCE_KEYBOARD for joystick so don't bother checking the e.getSource()
type = JE_JOYSTICK;
break;
default:
if (e.isSystem()) {
type = JE_SYS_KEY;
Expand All @@ -337,7 +343,7 @@ final public boolean onKey(View v, int keyCode, KeyEvent e) {
break;
}

// _scummvm.displayMessageOnOSD("GetKey: " + keyCode + " unic=" + eventUnicodeChar+ " arg3= " + (eventUnicodeChar& KeyCharacterMap.COMBINING_ACCENT_MASK));
//_scummvm.displayMessageOnOSD("GetKey: " + keyCode + " unic=" + eventUnicodeChar+ " arg3= " + (eventUnicodeChar& KeyCharacterMap.COMBINING_ACCENT_MASK));

// look in events.cpp for how this is handled
_scummvm.pushEvent(type,
Expand Down
192 changes: 184 additions & 8 deletions backends/platform/android/org/scummvm/scummvm/ScummVMEventsModern.java
@@ -1,26 +1,202 @@
package org.scummvm.scummvm;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.InputDevice;

import androidx.annotation.NonNull;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

// A class that extends the basic ScummVMEventsBase, supporting Android APIs > HONEYCOMB_MR1 (API 12)
public class ScummVMEventsModern extends ScummVMEventsBase {

private static final int MSG_REPEAT = 3;
private static final int REPEAT_INTERVAL = 20; // ~50 keys per second
private static final int REPEAT_START_DELAY = 40;

public ScummVMEventsModern(Context context, ScummVM scummvm, MouseHelper mouseHelper) {
super(context, scummvm, mouseHelper);
}

// Custom handler code (to avoid mem leaks, see warning "This Handler Class Should Be Static Or Leaks Might Occur”) based on:
// https://stackoverflow.com/a/27826094
public static class ScummVMEventsModernHandler extends Handler {

private final WeakReference<ScummVMEventsModern> mListenerReference;

public ScummVMEventsModernHandler(ScummVMEventsModern listener) {
mListenerReference = new WeakReference<>(listener);
}

@Override
public synchronized void handleMessage(@NonNull Message msg) {
ScummVMEventsModern listener = mListenerReference.get();
if(listener != null) {
switch (msg.what) {
case MSG_REPEAT:
if (listener.repeatMove()) {
Message repeat = Message.obtain(this, MSG_REPEAT);
sendMessageDelayed(repeat, REPEAT_INTERVAL);
}
break;
}
}
}

public void clear() {
this.removeCallbacksAndMessages(null);
}
}

@Override
public boolean onGenericMotionEvent(MotionEvent e) {
if ((e.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
_scummvm.pushEvent(JE_JOYSTICK, e.getAction(),
(int)(e.getAxisValue(MotionEvent.AXIS_X)*100),
(int)(e.getAxisValue(MotionEvent.AXIS_Y)*100),
0, 0, 0);
return true;
public void clearEventHandler() {
super.clearEventHandler();
mHandler.clear();
}

private ScummVMEventsModernHandler mHandler = new ScummVMEventsModernHandler(this);
private float repeatingX = 0.0f;
private float repeatingY = 0.0f;

private static float getCenteredAxis(MotionEvent event, InputDevice device, int axis, int historyPos) {
final InputDevice.MotionRange range = device.getMotionRange(axis, event.getSource());
final int actionPointerIndex = event.getActionIndex();

// A joystick at rest does not always report an absolute position of
// (0,0). Use the getFlat() method to determine the range of values
// bounding the joystick axis center.
if (range != null) {
final float flat = range.getFlat();

// if (axis == MotionEvent.AXIS_X
// || axis == MotionEvent.AXIS_HAT_X
// || axis == MotionEvent.AXIS_Z) {
// Log.d(ScummVM.LOG_TAG, "Flat X= " + flat);
// } else {
// Log.d(ScummVM.LOG_TAG, "Flat Y= " + flat);
// }

float axisVal = (historyPos < 0) ? event.getAxisValue( range.getAxis(), actionPointerIndex) : event.getHistoricalAxisValue( range.getAxis(), actionPointerIndex, historyPos);
// Normalize
final float value = (axisVal - range.getMin() ) / range.getRange() * 2.0f - 1.0f;

// Ignore axis values that are within the 'flat' region of the
// joystick axis center.
if (Math.abs(value) > flat) {
return value;
}
}
return 0;
}

return false;
private void removeMessages() {
if (mHandler != null) {
mHandler.removeMessages(MSG_REPEAT);
}
}

private boolean repeatMove() {
_scummvm.pushEvent(JE_JOYSTICK, MotionEvent.ACTION_MOVE,
(int) (repeatingX * 100),
(int) (repeatingY * 100),
0, 0, 0);
return true;
}

private void processJoystickInput(MotionEvent event, int historyPos) {

InputDevice inputDevice = event.getDevice();

// Calculate the horizontal distance to move by
// using the input value from one of these physical controls:
// the left control stick, hat axis, or the right control stick.
float x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_X, historyPos);
//Log.d(ScummVM.LOG_TAG, "JOYSTICK - LEFT: x= " +x);
if (x == 0) {
x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_X, historyPos);
//Log.d(ScummVM.LOG_TAG, "JOYSTICK - HAT: x= " +x);
}
if (x == 0) {
x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Z, historyPos);
//Log.d(ScummVM.LOG_TAG, "JOYSTICK - RIGHT: x= " +x);
}

// Calculate the vertical distance to move by
// using the input value from one of these physical controls:
// the left control stick, hat switch, or the right control stick.
float y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Y, historyPos);
//Log.d(ScummVM.LOG_TAG, "JOYSTICK - LEFT: y= " +y);
if (y == 0) {
y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_Y, historyPos);
//Log.d(ScummVM.LOG_TAG, "JOYSTICK - HAT: y= " +y);
}
if (y == 0) {
y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_RZ, historyPos);
//Log.d(ScummVM.LOG_TAG, "JOYSTICK - RIGHT: y= " +y);
}

// extra filter to stop repetition in order to avoid cases when android does not send onGenericMotionEvent()
// for small x or y (while abs is still larger than range.getflat())
// In such case we would end up with a slow moving "mouse" cursor - so we need this extra filter
if (Math.abs(x * 100) < 20.0f && Math.abs(y * 100) < 20.0f) {
//Log.d(ScummVM.LOG_TAG, "JOYSTICK - pushEvent(): STOPPED: " + (int)(x * 100) + " y= " + (int)(y * 100));
removeMessages();
// do the move anyway, just don't repeat
repeatMove();
repeatingX = 0.0f;
repeatingY = 0.0f;
} else {
//Log.d(ScummVM.LOG_TAG, "JOYSTICK - pushEvent(): x= " + (int)(x * 100) + " y= " + (int)(y * 100));
if (repeatingX != 0.0f || repeatingY != 0.0f) {
// already repeating - just update the movement co-ords
repeatingX = x;
repeatingY = y;
} else {
// start repeating
//removeMessages();
repeatingX = x;
repeatingY = y;
Message msg = mHandler.obtainMessage(MSG_REPEAT);
mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
repeatMove();
}
}
}

@Override
public boolean onGenericMotionEvent(MotionEvent event) {
// Check that the event came from a joystick
if (((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
|| (event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0)) {
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_MOVE) {

// Process all historical movement samples in the batch
final int historySize = event.getHistorySize();

// Process the movements starting from the
// earliest historical position in the batch
for (int i = 0; i < historySize; i++) {
// Process the event at historical position i
//Log.d(ScummVM.LOG_TAG, "JOYSTICK - onGenericMotionEvent(m) hist: ");
processJoystickInput(event, i);
}

// Process the current movement sample in the batch (position -1)
//Log.d(ScummVM.LOG_TAG, "JOYSTICK - onGenericMotionEvent(m): " );
processJoystickInput(event, -1);
return true;
}
}
// this basically returns false since the super just returns false
return super.onGenericMotionEvent(event);
}
}

0 comments on commit 64eb1c0

Please sign in to comment.