aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
authorBogDan Vatra <bogdan@kde.org>2015-10-14 17:01:20 +0300
committerAndy Nichols <andy.nichols@theqtcompany.com>2015-10-26 12:42:25 +0000
commit63527552d2070be768a0bc5d07a208053a5cd8fa (patch)
treeb7536e329ce95d86a858d852225b4d1e553fdffe /src/plugins
parentc32e995bc79035e600893e91fdcec984f8293c75 (diff)
Say hello to android gamepad support
Change-Id: I3712c6846c671a1aae8ca3eeae5a80eddd7a558b Reviewed-by: Andy Nichols <andy.nichols@theqtcompany.com>
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/gamepads/android/android.pro2
-rw-r--r--src/plugins/gamepads/android/jar/bundledjar.pro3
-rw-r--r--src/plugins/gamepads/android/jar/distributedjar.pro2
-rw-r--r--src/plugins/gamepads/android/jar/jar.pri16
-rw-r--r--src/plugins/gamepads/android/jar/jar.pro2
-rw-r--r--src/plugins/gamepads/android/jar/src/org/qtproject/qt5/android/gamepad/QtGamepad.java109
-rw-r--r--src/plugins/gamepads/android/src/android.json3
-rw-r--r--src/plugins/gamepads/android/src/main.cpp61
-rw-r--r--src/plugins/gamepads/android/src/qandroidgamepadbackend.cpp538
-rw-r--r--src/plugins/gamepads/android/src/qandroidgamepadbackend_p.h129
-rw-r--r--src/plugins/gamepads/android/src/src.pro17
-rw-r--r--src/plugins/gamepads/gamepads.pro3
12 files changed, 884 insertions, 1 deletions
diff --git a/src/plugins/gamepads/android/android.pro b/src/plugins/gamepads/android/android.pro
new file mode 100644
index 0000000..6ad7e3b
--- /dev/null
+++ b/src/plugins/gamepads/android/android.pro
@@ -0,0 +1,2 @@
+TEMPLATE=subdirs
+SUBDIRS += jar src
diff --git a/src/plugins/gamepads/android/jar/bundledjar.pro b/src/plugins/gamepads/android/jar/bundledjar.pro
new file mode 100644
index 0000000..65af1bb
--- /dev/null
+++ b/src/plugins/gamepads/android/jar/bundledjar.pro
@@ -0,0 +1,3 @@
+TARGET = QtAndroidGamepad-bundled
+CONFIG += bundled_jar_file
+include(jar.pri)
diff --git a/src/plugins/gamepads/android/jar/distributedjar.pro b/src/plugins/gamepads/android/jar/distributedjar.pro
new file mode 100644
index 0000000..d34c90c
--- /dev/null
+++ b/src/plugins/gamepads/android/jar/distributedjar.pro
@@ -0,0 +1,2 @@
+TARGET = QtAndroidGamepad
+include(jar.pri)
diff --git a/src/plugins/gamepads/android/jar/jar.pri b/src/plugins/gamepads/android/jar/jar.pri
new file mode 100644
index 0000000..13d36ac
--- /dev/null
+++ b/src/plugins/gamepads/android/jar/jar.pri
@@ -0,0 +1,16 @@
+load(qt_build_paths)
+CONFIG += java
+
+DESTDIR = $$MODULE_BASE_OUTDIR/jar
+
+API_VERSION = android-16
+
+JAVACLASSPATH += $$PWD/src
+
+JAVASOURCES += $$PWD/src/org/qtproject/qt5/android/gamepad/QtGamepad.java
+
+# install
+target.path = $$[QT_INSTALL_PREFIX]/jar
+INSTALLS += target
+
+OTHER_FILES += $$JAVASOURCES
diff --git a/src/plugins/gamepads/android/jar/jar.pro b/src/plugins/gamepads/android/jar/jar.pro
new file mode 100644
index 0000000..923e757
--- /dev/null
+++ b/src/plugins/gamepads/android/jar/jar.pro
@@ -0,0 +1,2 @@
+TEMPLATE=subdirs
+SUBDIRS += distributedjar.pro bundledjar.pro
diff --git a/src/plugins/gamepads/android/jar/src/org/qtproject/qt5/android/gamepad/QtGamepad.java b/src/plugins/gamepads/android/jar/src/org/qtproject/qt5/android/gamepad/QtGamepad.java
new file mode 100644
index 0000000..513a3f0
--- /dev/null
+++ b/src/plugins/gamepads/android/jar/src/org/qtproject/qt5/android/gamepad/QtGamepad.java
@@ -0,0 +1,109 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 BogDan Vatra <bogdan@kde.org>
+**
+** This file is part of the Qt Gamepad module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+package org.qtproject.qt5.android.gamepad;
+
+import android.app.Activity;
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+
+
+public class QtGamepad {
+ private long m_nativePtr = 0;
+ private InputManager m_manager;
+ Activity m_activity;
+ private InputManager.InputDeviceListener m_listener = new InputManager.InputDeviceListener() {
+ @Override
+ public void onInputDeviceAdded(int i) {
+ synchronized (QtGamepad.this) {
+ QtGamepad.onInputDeviceAdded(m_nativePtr, i);
+ }
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int i) {
+ synchronized (QtGamepad.this) {
+ QtGamepad.onInputDeviceRemoved(m_nativePtr, i);
+ }
+ }
+
+ @Override
+ public void onInputDeviceChanged(int i) {
+ synchronized (QtGamepad.this) {
+ QtGamepad.onInputDeviceChanged(m_nativePtr, i);
+ }
+ }
+ };
+
+ QtGamepad(Activity activity)
+ {
+ m_manager = (InputManager) activity.getSystemService(Context.INPUT_SERVICE);
+ m_activity = activity;
+ }
+
+ public void register(long nativePtr)
+ {
+ synchronized (this) {
+ if (m_manager != null) {
+ m_nativePtr = nativePtr;
+ m_activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ m_manager.registerInputDeviceListener(m_listener, new Handler());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+ }
+ }
+
+ public void unregister()
+ {
+ synchronized (this) {
+ if (m_manager != null) {
+ m_nativePtr = 0;
+ m_manager.unregisterInputDeviceListener(m_listener);
+ }
+ }
+ }
+
+ private static native void onInputDeviceAdded(long nativePtr, int deviceId);
+ private static native void onInputDeviceRemoved(long nativePtr, int deviceId);
+ private static native void onInputDeviceChanged(long nativePtr, int deviceId);
+}
diff --git a/src/plugins/gamepads/android/src/android.json b/src/plugins/gamepads/android/src/android.json
new file mode 100644
index 0000000..6843bd3
--- /dev/null
+++ b/src/plugins/gamepads/android/src/android.json
@@ -0,0 +1,3 @@
+{
+ "Keys": [ "android" ]
+}
diff --git a/src/plugins/gamepads/android/src/main.cpp b/src/plugins/gamepads/android/src/main.cpp
new file mode 100644
index 0000000..20cc646
--- /dev/null
+++ b/src/plugins/gamepads/android/src/main.cpp
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gamepad module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtGamepad/private/qgamepadbackendfactory_p.h>
+#include <QtGamepad/private/qgamepadbackendplugin_p.h>
+
+#include "qandroidgamepadbackend_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidGamepadBackendPlugin : public QGamepadBackendPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID QtGamepadBackendFactoryInterface_iid FILE "android.json")
+public:
+ QGamepadBackend *create(const QString &key, const QStringList &paramList) Q_DECL_OVERRIDE;
+};
+
+QGamepadBackend *QAndroidGamepadBackendPlugin::create(const QString &key, const QStringList &paramList) {
+ Q_UNUSED(key)
+ Q_UNUSED(paramList)
+
+ return new QAndroidGamepadBackend();
+}
+
+QT_END_NAMESPACE
+
+#include "main.moc"
diff --git a/src/plugins/gamepads/android/src/qandroidgamepadbackend.cpp b/src/plugins/gamepads/android/src/qandroidgamepadbackend.cpp
new file mode 100644
index 0000000..e8dbc0f
--- /dev/null
+++ b/src/plugins/gamepads/android/src/qandroidgamepadbackend.cpp
@@ -0,0 +1,538 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 BogDan Vatra <bogdan@kde.org>
+**
+** This file is part of the Qt Gamepad module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "qandroidgamepadbackend_p.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QEvent>
+#include <QtCore/QPair>
+#include <QtCore/QThread>
+#include <QtCore/QVector>
+
+#include <functional>
+#include <vector>
+
+#include <QVariant>
+#include <jni.h>
+
+QT_BEGIN_NAMESPACE
+namespace {
+ const QLatin1String AXES_KEY("axes");
+ const QLatin1String BUTTONS_KEY("buttons");
+
+ class FunctionEvent : public QEvent
+ {
+ typedef std::function<void()> Function;
+ public:
+ explicit FunctionEvent(const Function &func)
+ : QEvent(QEvent::User)
+ , m_function(func)
+ {}
+ inline void call() { m_function(); }
+ static inline void runOnQtThread(QObject *receiver, const Function &func) {
+ if (qApp->thread() == QThread::currentThread())
+ func();
+ else
+ qApp->postEvent(receiver, new FunctionEvent(func));
+ }
+
+ private:
+ Function m_function;
+ };
+
+ const char keyEventClass[] = "android/view/KeyEvent";
+ inline int keyField(const char *field)
+ {
+ return QJNIObjectPrivate::getStaticField<jint>(keyEventClass, field);
+ }
+
+ const char motionEventClass[] = "android/view/MotionEvent";
+ inline int motionField(const char *field)
+ {
+ return QJNIObjectPrivate::getStaticField<jint>(motionEventClass, field);
+ }
+
+ const char inputDeviceClass[] = "android/view/InputDevice";
+ inline int inputDeviceField(const char *field)
+ {
+ return QJNIObjectPrivate::getStaticField<jint>(inputDeviceClass, field);
+ }
+
+
+ struct DefaultMapping : public QAndroidGamepadBackend::Mapping {
+ DefaultMapping()
+ {
+ buttonsMap[keyField("KEYCODE_BUTTON_A")] = QGamepadManager::ButtonA;
+ buttonsMap[keyField("KEYCODE_BUTTON_B")] = QGamepadManager::ButtonB;
+ buttonsMap[keyField("KEYCODE_BUTTON_X")] = QGamepadManager::ButtonX;
+ buttonsMap[keyField("KEYCODE_BUTTON_Y")] = QGamepadManager::ButtonY;
+ buttonsMap[keyField("KEYCODE_BUTTON_L1")] = QGamepadManager::ButtonL1;
+ buttonsMap[keyField("KEYCODE_BUTTON_R1")] = QGamepadManager::ButtonR1;
+ buttonsMap[keyField("KEYCODE_BUTTON_L2")] = QGamepadManager::ButtonL2;
+ buttonsMap[keyField("KEYCODE_BUTTON_R2")] = QGamepadManager::ButtonR2;
+ buttonsMap[keyField("KEYCODE_BUTTON_SELECT")] = QGamepadManager::ButtonSelect;
+ buttonsMap[keyField("KEYCODE_BUTTON_START")] = QGamepadManager::ButtonStart;
+ buttonsMap[keyField("KEYCODE_BUTTON_THUMBL")] = QGamepadManager::ButtonL3;
+ buttonsMap[keyField("KEYCODE_BUTTON_THUMBR")] = QGamepadManager::ButtonR3;
+ buttonsMap[keyField("KEYCODE_DPAD_UP")] = QGamepadManager::ButtonUp;
+ buttonsMap[keyField("KEYCODE_DPAD_DOWN")] = QGamepadManager::ButtonDown;
+ buttonsMap[keyField("KEYCODE_DPAD_RIGHT")] = QGamepadManager::ButtonRight;
+ buttonsMap[keyField("KEYCODE_DPAD_LEFT")] = QGamepadManager::ButtonLeft;
+ buttonsMap[keyField("KEYCODE_DPAD_CENTER")] = QGamepadManager::ButtonCenter;
+ buttonsMap[keyField("KEYCODE_BUTTON_MODE")] = QGamepadManager::ButtonGuide;
+
+ if (QtAndroidPrivate::androidSdkVersion() >= 12) {
+ axisMap[motionField("AXIS_X")].gamepadAxis = QGamepadManager::AxisLeftX;
+ axisMap[motionField("AXIS_Y")].gamepadAxis = QGamepadManager::AxisLeftY;
+ axisMap[motionField("AXIS_HAT_X")].gamepadAxis = QGamepadManager::AxisLeftX;
+ axisMap[motionField("AXIS_HAT_Y")].gamepadAxis = QGamepadManager::AxisLeftY;
+ axisMap[motionField("AXIS_Z")].gamepadAxis = QGamepadManager::AxisRightX;
+ axisMap[motionField("AXIS_RZ")].gamepadAxis = QGamepadManager::AxisRightY;
+
+ allAndroidAxes.push_back(motionField("AXIS_X"));
+ allAndroidAxes.push_back(motionField("AXIS_Y"));
+ allAndroidAxes.push_back(motionField("AXIS_Z"));
+ allAndroidAxes.push_back(motionField("AXIS_RZ"));
+ allAndroidAxes.push_back(motionField("AXIS_BRAKE"));
+ allAndroidAxes.push_back(motionField("AXIS_GAS"));
+
+ for (int i = 1; i < 16; ++i)
+ allAndroidAxes.push_back(motionField(QByteArray("AXIS_GENERIC_").append(QByteArray::number(i)).constData()));
+
+ allAndroidAxes.push_back(motionField("AXIS_HAT_X"));
+ allAndroidAxes.push_back(motionField("AXIS_HAT_Y"));
+ allAndroidAxes.push_back(motionField("AXIS_LTRIGGER"));
+ allAndroidAxes.push_back(motionField("AXIS_RTRIGGER"));
+ allAndroidAxes.push_back(motionField("AXIS_RUDDER"));
+ allAndroidAxes.push_back(motionField("AXIS_THROTTLE"));
+ allAndroidAxes.push_back(motionField("AXIS_WHEEL"));
+ }
+
+ acceptedSources.push_back(inputDeviceField("SOURCE_DPAD"));
+ if (QtAndroidPrivate::androidSdkVersion() >= 12) {
+ acceptedSources.push_back(inputDeviceField("SOURCE_GAMEPAD"));
+ acceptedSources.push_back(inputDeviceField("SOURCE_CLASS_JOYSTICK"));
+ if (QtAndroidPrivate::androidSdkVersion() >= 21) {
+ acceptedSources.push_back(inputDeviceField("SOURCE_HDMI"));
+ }
+ }
+
+ ACTION_DOWN = keyField("ACTION_DOWN");
+ ACTION_UP = keyField("ACTION_UP");
+ ACTION_MULTIPLE = keyField("ACTION_MULTIPLE");
+ FLAG_LONG_PRESS = keyField("FLAG_LONG_PRESS");
+ }
+ std::vector<int> acceptedSources;
+ std::vector<int> allAndroidAxes;
+ int ACTION_DOWN, ACTION_MULTIPLE, ACTION_UP, FLAG_LONG_PRESS;
+ };
+
+ void onInputDeviceAdded(JNIEnv *, jclass, jlong qtNativePtr, int deviceId)
+ {
+ if (!qtNativePtr)
+ return;
+ reinterpret_cast<QAndroidGamepadBackend*>(qtNativePtr)->addDevice(deviceId);
+ }
+ void onInputDeviceRemoved(JNIEnv *, jclass, jlong qtNativePtr, int deviceId)
+ {
+ if (!qtNativePtr)
+ return;
+ reinterpret_cast<QAndroidGamepadBackend*>(qtNativePtr)->removeDevice(deviceId);
+ }
+ void onInputDeviceChanged(JNIEnv *, jclass, jlong qtNativePtr, int deviceId)
+ {
+ if (!qtNativePtr)
+ return;
+ reinterpret_cast<QAndroidGamepadBackend*>(qtNativePtr)->updateDevice(deviceId);
+ }
+
+ static JNINativeMethod methods[] = {
+ {"onInputDeviceAdded", "(JI)V", (void *)onInputDeviceAdded},
+ {"onInputDeviceRemoved", "(JI)V", (void *)onInputDeviceRemoved},
+ {"onInputDeviceChanged", "(JI)V", (void *)onInputDeviceChanged}
+ };
+
+ const char qtGamePadClassName[] = "org/qtproject/qt5/android/gamepad/QtGamepad";
+
+ inline void setAxisInfo(QJNIObjectPrivate &motionRange, QAndroidGamepadBackend::Mapping::AndroidAxisInfo &info)
+ {
+ if (motionRange.isValid()) {
+ info.flatArea = motionRange.callMethod<jfloat>("getFlat", "()F");
+ info.minValue = motionRange.callMethod<jfloat>("getMin", "()F");
+ info.maxValue = motionRange.callMethod<jfloat>("getMax", "()F");
+ info.fuzz = motionRange.callMethod<jfloat>("getFuzz", "()F");
+ } else {
+ info.flatArea = 0;
+ }
+ }
+
+} // namespace
+
+Q_GLOBAL_STATIC(DefaultMapping, g_defaultMapping)
+
+QAndroidGamepadBackend::QAndroidGamepadBackend(QObject *parent)
+ : QGamepadBackend(parent)
+{
+ QtAndroidPrivate::registerGenericMotionEventListener(this);
+ QtAndroidPrivate::registerKeyEventListener(this);
+}
+
+QAndroidGamepadBackend::~QAndroidGamepadBackend()
+{
+ QtAndroidPrivate::unregisterGenericMotionEventListener(this);
+ QtAndroidPrivate::unregisterKeyEventListener(this);
+}
+
+void QAndroidGamepadBackend::addDevice(int deviceId)
+{
+ if (deviceId == -1)
+ return;
+
+ QMutexLocker lock(&m_mutex);
+ QJNIObjectPrivate inputDevice = QJNIObjectPrivate::callStaticObjectMethod(inputDeviceClass, "getDevice", "(I)Landroid/view/InputDevice;", deviceId);
+ int sources = inputDevice.callMethod<jint>("getSources", "()I");
+ bool acceptable = false;
+ for (int source : g_defaultMapping()->acceptedSources) {
+ if ( (source & sources) == source) {
+ acceptable = true;
+ break;
+ }
+ }
+
+ if (acceptable) {
+ m_devices.insert(deviceId, *g_defaultMapping());
+ int productId = inputDevice.callMethod<jint>("getProductId", "()I");
+ m_devices[deviceId].productId = productId;
+ if (productId) {
+ QVariant settings = readSettings(productId);
+ if (!settings.isNull()) {
+ auto &deviceInfo = m_devices[deviceId];
+ deviceInfo.needsConfigure = false;
+
+ QVariantMap data = settings.toMap()[AXES_KEY].toMap();
+ for (QVariantMap::const_iterator it = data.begin(); it != data.end(); ++it)
+ deviceInfo.axisMap[it.key().toInt()].gamepadAxis = QGamepadManager::GamepadAxis(it.value().toInt());
+
+ data = settings.toMap()[BUTTONS_KEY].toMap();
+ for (QVariantMap::const_iterator it = data.begin(); it != data.end(); ++it)
+ deviceInfo.buttonsMap[it.key().toInt()] = QGamepadManager::GamepadButton(it.value().toInt());
+ }
+ }
+ FunctionEvent::runOnQtThread(this, [this, deviceId]{
+ emit gamepadAdded(deviceId);
+ });
+ }
+}
+
+void QAndroidGamepadBackend::updateDevice(int deviceId)
+{
+ Q_UNUSED(deviceId)
+ //QMutexLocker lock(&m_mutex);
+}
+
+void QAndroidGamepadBackend::removeDevice(int deviceId)
+{
+ QMutexLocker lock(&m_mutex);
+ if (m_devices.remove(deviceId)) {
+ FunctionEvent::runOnQtThread(this, [this, deviceId]{
+ emit gamepadRemoved(deviceId);
+ });
+ }
+}
+
+bool QAndroidGamepadBackend::event(QEvent *ev)
+{
+ if (ev->type() == QEvent::User) {
+ static_cast<FunctionEvent*>(ev)->call();
+ return true;
+ }
+ return QGamepadBackend::event(ev);
+}
+
+bool QAndroidGamepadBackend::isConfigurationNeeded(int deviceId)
+{
+ QMutexLocker lock(&m_mutex);
+ auto it = m_devices.find(deviceId);
+ if (it == m_devices.end())
+ return false;
+ return it->needsConfigure;
+}
+
+bool QAndroidGamepadBackend::configureButton(int deviceId, QGamepadManager::GamepadButton button)
+{
+ QMutexLocker lock(&m_mutex);
+ auto it = m_devices.find(deviceId);
+ if (it == m_devices.end())
+ return false;
+
+ it.value().calibrateButton = button;
+ return true;
+}
+
+bool QAndroidGamepadBackend::configureAxis(int deviceId, QGamepadManager::GamepadAxis axis)
+{
+ QMutexLocker lock(&m_mutex);
+ auto it = m_devices.find(deviceId);
+ if (it == m_devices.end())
+ return false;
+
+ it.value().calibrateAxis = axis;
+ return true;
+}
+
+bool QAndroidGamepadBackend::setCancelConfigureButton(int deviceId, QGamepadManager::GamepadButton button)
+{
+ QMutexLocker lock(&m_mutex);
+ auto it = m_devices.find(deviceId);
+ if (it == m_devices.end())
+ return false;
+
+ it.value().cancelConfigurationButton = button;
+ return true;
+}
+
+void QAndroidGamepadBackend::resetConfiguration(int deviceId)
+{
+ QMutexLocker lock(&m_mutex);
+ auto it = m_devices.find(deviceId);
+ if (it == m_devices.end())
+ return;
+
+ int productId = it.value().productId;
+ it.value() = *g_defaultMapping();
+ it.value().productId = productId;
+}
+
+bool QAndroidGamepadBackend::handleKeyEvent(jobject event)
+{
+ QJNIObjectPrivate ev(event);
+ QMutexLocker lock(&m_mutex);
+ const int deviceId = ev.callMethod<jint>("getDeviceId", "()I");
+ const auto deviceIt = m_devices.find(deviceId);
+ if (deviceIt == m_devices.end())
+ return false;
+
+ const int action = ev.callMethod<jint>("getAction", "()I");
+ if (action != g_defaultMapping()->ACTION_DOWN &&
+ action != g_defaultMapping()->ACTION_UP) {
+ return false;
+ }
+ const int flags = ev.callMethod<jint>("getFlags", "()I");
+ if ((flags & g_defaultMapping()->FLAG_LONG_PRESS) == g_defaultMapping()->FLAG_LONG_PRESS)
+ return false;
+
+ const int keyCode = ev.callMethod<jint>("getKeyCode", "()I");
+ auto &deviceMap = deviceIt.value();
+
+ if (deviceMap.cancelConfigurationButton != QGamepadManager::ButtonInvalid &&
+ (deviceMap.calibrateButton != QGamepadManager::ButtonInvalid ||
+ deviceMap.calibrateAxis != QGamepadManager::AxisInvalid)) {
+
+ const auto buttonsMapIt = deviceMap.buttonsMap.find(keyCode);
+ if (buttonsMapIt != deviceMap.buttonsMap.end() &&
+ deviceMap.cancelConfigurationButton == buttonsMapIt.value()) {
+ deviceMap.calibrateButton = QGamepadManager::ButtonInvalid;
+ deviceMap.calibrateAxis = QGamepadManager::AxisInvalid;
+ FunctionEvent::runOnQtThread(this, [this, deviceId]{
+ emit configurationCanceled(deviceId);
+ });
+ return true;
+ }
+ }
+
+ if (deviceMap.calibrateButton != QGamepadManager::ButtonInvalid) {
+ deviceMap.buttonsMap[keyCode] = deviceMap.calibrateButton;
+ auto but = deviceMap.calibrateButton;
+ deviceMap.calibrateButton = QGamepadManager::ButtonInvalid;
+ saveData(deviceMap);
+ FunctionEvent::runOnQtThread(this, [this, deviceId, but]{
+ emit buttonConfigured(deviceId, but);
+ });
+ }
+
+ const auto buttonsMapIt = deviceMap.buttonsMap.find(keyCode);
+ if (buttonsMapIt == deviceMap.buttonsMap.end())
+ return false;
+
+ const auto button = buttonsMapIt.value();
+ if (action == g_defaultMapping()->ACTION_DOWN) {
+ FunctionEvent::runOnQtThread(this, [this, deviceId, button]{
+ emit gamepadButtonPressed(deviceId, button, 1.0);
+ });
+ } else {
+ FunctionEvent::runOnQtThread(this, [this, deviceId, button]{
+ emit gamepadButtonReleased(deviceId, button);
+ });
+ }
+ return true;
+}
+
+bool QAndroidGamepadBackend::handleGenericMotionEvent(jobject event)
+{
+ // GenericMotionEvent was introduced in API-12
+ Q_ASSERT(QtAndroidPrivate::androidSdkVersion() >= 12);
+
+ QJNIObjectPrivate ev(event);
+ QMutexLocker lock(&m_mutex);
+ const int deviceId = ev.callMethod<jint>("getDeviceId", "()I");
+ const auto deviceIt = m_devices.find(deviceId);
+ if (deviceIt == m_devices.end())
+ return false;
+
+ auto &deviceMap = deviceIt.value();
+ if (deviceMap.calibrateAxis != QGamepadManager::AxisInvalid) {
+ double lastValue = 0;
+ int lastAxis = -1;
+ for (int axis : g_defaultMapping()->allAndroidAxes) {
+ double value = fabs(ev.callMethod<jfloat>("getAxisValue", "(I)F", axis));
+ if (value > lastValue) {
+ lastValue = value;
+ lastAxis = axis;
+ }
+ }
+
+ if (!lastValue || lastAxis == -1)
+ return false;
+
+ deviceMap.axisMap[lastAxis].gamepadAxis = deviceMap.calibrateAxis;
+ auto axis = deviceMap.calibrateAxis;
+ deviceMap.calibrateAxis = QGamepadManager::AxisInvalid;
+ saveData(deviceMap);
+ FunctionEvent::runOnQtThread(this, [this, deviceId, axis]{
+ emit axisConfigured(deviceId, axis);
+ });
+ }
+
+ typedef QPair<QGamepadManager::GamepadAxis, double> GamepadAxisValue;
+ QVector<GamepadAxisValue> values;
+ for (auto it = deviceMap.axisMap.begin(); it != deviceMap.axisMap.end(); ++it) {
+ auto &axisInfo = it.value();
+ if (axisInfo.flatArea == -1) {
+ // compute the range & flat area
+ QJNIObjectPrivate device(ev.callObjectMethod("getDevice", "()Landroid/view/InputDevice;"));
+ if (device.isValid()) {
+ const int source = ev.callMethod<jint>("getSource", "()I");
+ QJNIObjectPrivate motionRange = device.callObjectMethod("getMotionRange","(II)Landroid/view/InputDevice$MotionRange;", it.key(), source);
+ setAxisInfo(motionRange, axisInfo);
+ }
+ }
+
+ const int historicalValues = ev.callMethod<jint>("getHistorySize", "()I");
+ for (int i = 0; i < historicalValues; ++i) {
+ double value = ev.callMethod<jfloat>("getHistoricalAxisValue", "(II)F", it.key(), i);
+ if (axisInfo.setValue(value))
+ values.push_back(GamepadAxisValue(axisInfo.gamepadAxis, axisInfo.lastValue));
+ }
+ double value = ev.callMethod<jfloat>("getAxisValue", "(I)F", it.key());
+ if (axisInfo.setValue(value))
+ values.push_back(GamepadAxisValue(axisInfo.gamepadAxis, axisInfo.lastValue));
+ }
+
+ if (!values.isEmpty()) {
+ FunctionEvent::runOnQtThread(this, [this, deviceId, values]{
+ foreach (const auto &axisValue, values)
+ emit gamepadAxisMoved(deviceId, axisValue.first, axisValue.second);
+ });
+ }
+ return false;
+}
+
+bool QAndroidGamepadBackend::start()
+{
+ {
+ QMutexLocker lock(&m_mutex);
+ if (QtAndroidPrivate::androidSdkVersion() >= 16) {
+ if (!m_qtGamepad.isValid())
+ m_qtGamepad = QJNIObjectPrivate(qtGamePadClassName, "(Landroid/app/Activity;)V", QtAndroidPrivate::activity());
+ m_qtGamepad.callMethod<void>("register", "(J)V", jlong(this));
+ }
+ }
+
+ QJNIObjectPrivate ids = QJNIObjectPrivate::callStaticObjectMethod(inputDeviceClass, "getDeviceIds", "()[I");
+ jintArray jarr = jintArray(ids.object());
+ QJNIEnvironmentPrivate env;
+ size_t sz = env->GetArrayLength(jarr);
+ jint *buff = env->GetIntArrayElements(jarr, nullptr);
+ for (size_t i = 0; i < sz; ++i)
+ addDevice(buff[i]);
+ env->ReleaseIntArrayElements(jarr, buff, 0);
+ return true;
+}
+
+void QAndroidGamepadBackend::stop()
+{
+ QMutexLocker lock(&m_mutex);
+ if (QtAndroidPrivate::androidSdkVersion() >= 16 && m_qtGamepad.isValid())
+ m_qtGamepad.callMethod<void>("unregister", "()V");
+}
+
+void QAndroidGamepadBackend::saveData(const QAndroidGamepadBackend::Mapping &deviceInfo)
+{
+ if (!deviceInfo.productId)
+ return ;
+
+ QVariantMap settings, data;
+ for (auto it = deviceInfo.axisMap.begin(); it != deviceInfo.axisMap.end(); ++it)
+ data[QString::number(it.key())] = it.value().gamepadAxis;
+ settings[AXES_KEY] = data;
+
+ data.clear();
+ for (auto it = deviceInfo.buttonsMap.begin(); it != deviceInfo.buttonsMap.end(); ++it)
+ data[QString::number(it.key())] = it.value();
+ settings[BUTTONS_KEY] = data;
+
+ saveSettings(deviceInfo.productId, settings);
+}
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
+{
+ JNIEnv* env;
+ // get the JNIEnv pointer.
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
+ return JNI_ERR;
+
+ // search for Java class which declares the native methods
+ jclass javaClass = env->FindClass("org/qtproject/qt5/android/gamepad/QtGamepad");
+ if (!javaClass)
+ return JNI_ERR;
+
+ // register our native methods
+ if (env->RegisterNatives(javaClass, methods,
+ sizeof(methods) / sizeof(methods[0])) < 0) {
+ return JNI_ERR;
+ }
+ return JNI_VERSION_1_6;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/gamepads/android/src/qandroidgamepadbackend_p.h b/src/plugins/gamepads/android/src/qandroidgamepadbackend_p.h
new file mode 100644
index 0000000..b441317
--- /dev/null
+++ b/src/plugins/gamepads/android/src/qandroidgamepadbackend_p.h
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 BogDan Vatra <bogdan@kde.org>
+**
+** This file is part of the Qt Gamepad module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSDLGAMEPADCONTROLLER_H
+#define QSDLGAMEPADCONTROLLER_H
+
+#include <QtCore/QHash>
+#include <QtCore/QMutex>
+#include <QtCore/QSet>
+
+#include <QtCore/private/qjni_p.h>
+#include <QtCore/private/qjnihelpers_p.h>
+
+#include <QtGamepad/QGamepadManager>
+#include <QtGamepad/private/qgamepadbackend_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidGamepadBackend : public QGamepadBackend, public QtAndroidPrivate::GenericMotionEventListener, public QtAndroidPrivate::KeyEventListener
+{
+ Q_OBJECT
+public:
+ explicit QAndroidGamepadBackend(QObject *parent = 0);
+ ~QAndroidGamepadBackend();
+
+ void addDevice(int deviceId);
+ void updateDevice(int deviceId);
+ void removeDevice(int deviceId);
+
+ // QObject interface
+ bool event(QEvent *) override;
+
+ // QGamepadBackend interface
+ bool isConfigurationNeeded(int deviceId) override;
+ bool configureButton(int deviceId, QGamepadManager::GamepadButton button) override;
+ bool configureAxis(int deviceId, QGamepadManager::GamepadAxis axis) override;
+ bool setCancelConfigureButton(int deviceId, QGamepadManager::GamepadButton button) override;
+ void resetConfiguration(int deviceId) override;
+
+
+ // KeyEventListener interface
+ bool handleKeyEvent(jobject event) override;
+
+ // GenericMotionEventListener interface
+ bool handleGenericMotionEvent(jobject event) override;
+
+protected:
+ bool start();
+ void stop();
+
+public:
+ struct Mapping {
+ struct AndroidAxisInfo : public AxisInfo<double> {
+ AndroidAxisInfo() : AxisInfo(-1.0, 1.0) { }
+ AndroidAxisInfo(double minValue, double maxValue) : AxisInfo(minValue, maxValue) { }
+
+ inline bool setValue(double value)
+ {
+ if (minValue != -1.0 && maxValue != 1.0)
+ value = AxisInfo::normalized(value);
+
+ if (qAbs(value) <= flatArea)
+ value = 0;
+
+ if (qAbs(qAbs(value) - qAbs(lastValue)) <= fuzz)
+ return false;
+
+ lastValue = value;
+ return true;
+ }
+
+ double flatArea = -1;
+ double fuzz = 0;
+ double lastValue = 0;
+ };
+ QHash<int, AndroidAxisInfo> axisMap;
+ QHash<int, QGamepadManager::GamepadButton> buttonsMap;
+
+ QGamepadManager::GamepadButton calibrateButton = QGamepadManager::ButtonInvalid;
+ QGamepadManager::GamepadAxis calibrateAxis = QGamepadManager::AxisInvalid;
+ QGamepadManager::GamepadButton cancelConfigurationButton = QGamepadManager::ButtonInvalid;
+ int productId = 0;
+ bool needsConfigure = true;
+ };
+
+private:
+ void saveData(const Mapping &deviceInfo);
+
+private:
+ QMutex m_mutex;
+ QJNIObjectPrivate m_qtGamepad;
+ QHash<int, Mapping> m_devices;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSDLGAMEPADCONTROLLER_H
diff --git a/src/plugins/gamepads/android/src/src.pro b/src/plugins/gamepads/android/src/src.pro
new file mode 100644
index 0000000..31f8a28
--- /dev/null
+++ b/src/plugins/gamepads/android/src/src.pro
@@ -0,0 +1,17 @@
+TARGET = androidgamepad
+QT += core-private gamepad gamepad-private
+
+PLUGIN_TYPE = gamepads
+PLUGIN_CLASS_NAME = QAndroidGamepadBackendPlugin
+
+load(qt_plugin)
+
+HEADERS += \
+ qandroidgamepadbackend_p.h
+
+SOURCES += \
+ main.cpp \
+ qandroidgamepadbackend.cpp
+
+DISTFILES += \
+ android.json
diff --git a/src/plugins/gamepads/gamepads.pro b/src/plugins/gamepads/gamepads.pro
index 30347bb..980f81f 100644
--- a/src/plugins/gamepads/gamepads.pro
+++ b/src/plugins/gamepads/gamepads.pro
@@ -1,5 +1,6 @@
TEMPLATE = subdirs
config_sdl:SUBDIRS += sdl2
-contains(QT_CONFIG, evdev): SUBDIRS += evdev
+!android: contains(QT_CONFIG, evdev): SUBDIRS += evdev
win32: !wince*: SUBDIRS += xinput
ios: SUBDIRS += ios
+android: SUBDIRS += android