1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 BogDan Vatra <bogdan@kde.org>
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Gamepad module
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 #include "qandroidgamepadbackend_p.h"
37 
38 #include <QtCore/QCoreApplication>
39 #include <QtCore/QEvent>
40 #include <QtCore/QPair>
41 #include <QtCore/QThread>
42 #include <QtCore/QVector>
43 
44 #include <functional>
45 #include <vector>
46 
47 #include <QVariant>
48 #include <jni.h>
49 #include <math.h>
50 
51 QT_BEGIN_NAMESPACE
52 namespace {
53     const QLatin1String AXES_KEY("axes");
54     const QLatin1String BUTTONS_KEY("buttons");
55 
56     class FunctionEvent : public QEvent
57     {
58         typedef std::function<void()> Function;
59     public:
FunctionEvent(const Function & func)60         explicit FunctionEvent(const Function &func)
61             : QEvent(QEvent::User)
62             , m_function(func)
63         {}
call()64         inline void call() { m_function(); }
runOnQtThread(QObject * receiver,const Function & func)65         static inline void runOnQtThread(QObject *receiver, const Function &func) {
66             if (qApp->thread() == QThread::currentThread())
67                 func();
68             else
69                 qApp->postEvent(receiver, new FunctionEvent(func));
70         }
71 
72     private:
73         Function m_function;
74     };
75 
76     const char keyEventClass[] = "android/view/KeyEvent";
keyField(const char * field)77     inline int keyField(const char *field)
78     {
79         return QJNIObjectPrivate::getStaticField<jint>(keyEventClass, field);
80     }
81 
82     const char motionEventClass[] = "android/view/MotionEvent";
motionField(const char * field)83     inline int motionField(const char *field)
84     {
85         return QJNIObjectPrivate::getStaticField<jint>(motionEventClass, field);
86     }
87 
88     const char inputDeviceClass[] = "android/view/InputDevice";
inputDeviceField(const char * field)89     inline int inputDeviceField(const char *field)
90     {
91         return QJNIObjectPrivate::getStaticField<jint>(inputDeviceClass, field);
92     }
93 
94 
95     struct DefaultMapping : public QAndroidGamepadBackend::Mapping {
DefaultMapping__anona1af88740111::DefaultMapping96         DefaultMapping()
97         {
98             buttonsMap[keyField("KEYCODE_BUTTON_A")] = QGamepadManager::ButtonA;
99             buttonsMap[keyField("KEYCODE_BUTTON_B")] = QGamepadManager::ButtonB;
100             buttonsMap[keyField("KEYCODE_BUTTON_X")] = QGamepadManager::ButtonX;
101             buttonsMap[keyField("KEYCODE_BUTTON_Y")] = QGamepadManager::ButtonY;
102             buttonsMap[keyField("KEYCODE_BUTTON_L1")] = QGamepadManager::ButtonL1;
103             buttonsMap[keyField("KEYCODE_BUTTON_R1")] = QGamepadManager::ButtonR1;
104             buttonsMap[keyField("KEYCODE_BUTTON_L2")] = QGamepadManager::ButtonL2;
105             buttonsMap[keyField("KEYCODE_BUTTON_R2")] = QGamepadManager::ButtonR2;
106             buttonsMap[keyField("KEYCODE_BUTTON_SELECT")] = QGamepadManager::ButtonSelect;
107             buttonsMap[keyField("KEYCODE_BUTTON_START")] = QGamepadManager::ButtonStart;
108             buttonsMap[keyField("KEYCODE_BUTTON_THUMBL")] = QGamepadManager::ButtonL3;
109             buttonsMap[keyField("KEYCODE_BUTTON_THUMBR")] = QGamepadManager::ButtonR3;
110             buttonsMap[keyField("KEYCODE_DPAD_UP")] = QGamepadManager::ButtonUp;
111             buttonsMap[keyField("KEYCODE_DPAD_DOWN")] = QGamepadManager::ButtonDown;
112             buttonsMap[keyField("KEYCODE_DPAD_RIGHT")] = QGamepadManager::ButtonRight;
113             buttonsMap[keyField("KEYCODE_DPAD_LEFT")] = QGamepadManager::ButtonLeft;
114             buttonsMap[keyField("KEYCODE_DPAD_CENTER")] = QGamepadManager::ButtonCenter;
115             buttonsMap[keyField("KEYCODE_BUTTON_MODE")] = QGamepadManager::ButtonGuide;
116 
117             if (QtAndroidPrivate::androidSdkVersion() >= 12) {
118                 axisMap[motionField("AXIS_X")].gamepadAxis = QGamepadManager::AxisLeftX;
119                 axisMap[motionField("AXIS_Y")].gamepadAxis = QGamepadManager::AxisLeftY;
120                 axisMap[motionField("AXIS_HAT_X")].gamepadAxis = QGamepadManager::AxisLeftX;
121                 axisMap[motionField("AXIS_HAT_Y")].gamepadAxis = QGamepadManager::AxisLeftY;
122                 axisMap[motionField("AXIS_Z")].gamepadAxis = QGamepadManager::AxisRightX;
123                 axisMap[motionField("AXIS_RZ")].gamepadAxis = QGamepadManager::AxisRightY;
124                 {
125                     auto &axis = axisMap[motionField("AXIS_LTRIGGER")];
126                     axis.gamepadAxis = QGamepadManager::AxisInvalid;
127                     axis.gamepadMinButton = QGamepadManager::ButtonL2;
128                     axis.gamepadMaxButton = QGamepadManager::ButtonL2;
129                 }
130                 {
131                     auto &axis = axisMap[motionField("AXIS_RTRIGGER")];
132                     axis.gamepadAxis = QGamepadManager::AxisInvalid;
133                     axis.gamepadMinButton = QGamepadManager::ButtonR2;
134                     axis.gamepadMaxButton = QGamepadManager::ButtonR2;
135                 }
136 
137                 allAndroidAxes.push_back(motionField("AXIS_X"));
138                 allAndroidAxes.push_back(motionField("AXIS_Y"));
139                 allAndroidAxes.push_back(motionField("AXIS_Z"));
140                 allAndroidAxes.push_back(motionField("AXIS_RZ"));
141                 allAndroidAxes.push_back(motionField("AXIS_BRAKE"));
142                 allAndroidAxes.push_back(motionField("AXIS_GAS"));
143 
144                 for (int i = 1; i < 16; ++i)
145                     allAndroidAxes.push_back(motionField(QByteArray("AXIS_GENERIC_").append(QByteArray::number(i)).constData()));
146 
147                 allAndroidAxes.push_back(motionField("AXIS_HAT_X"));
148                 allAndroidAxes.push_back(motionField("AXIS_HAT_Y"));
149                 allAndroidAxes.push_back(motionField("AXIS_LTRIGGER"));
150                 allAndroidAxes.push_back(motionField("AXIS_RTRIGGER"));
151                 allAndroidAxes.push_back(motionField("AXIS_RUDDER"));
152                 allAndroidAxes.push_back(motionField("AXIS_THROTTLE"));
153                 allAndroidAxes.push_back(motionField("AXIS_WHEEL"));
154             }
155 
156             if (QtAndroidPrivate::androidSdkVersion() >= 12) {
157                 acceptedSources.push_back(inputDeviceField("SOURCE_GAMEPAD"));
158                 acceptedSources.push_back(inputDeviceField("SOURCE_CLASS_JOYSTICK"));
159                 if (QtAndroidPrivate::androidSdkVersion() >= 21) {
160                     acceptedSources.push_back(inputDeviceField("SOURCE_HDMI"));
161                 }
162             } else {
163                 acceptedSources.push_back(inputDeviceField("SOURCE_DPAD"));
164             }
165 
166             ACTION_DOWN = keyField("ACTION_DOWN");
167             ACTION_UP = keyField("ACTION_UP");
168             ACTION_MULTIPLE = keyField("ACTION_MULTIPLE");
169             FLAG_LONG_PRESS = keyField("FLAG_LONG_PRESS");
170         }
171         std::vector<int> acceptedSources;
172         std::vector<int> allAndroidAxes;
173         int ACTION_DOWN, ACTION_MULTIPLE, ACTION_UP, FLAG_LONG_PRESS;
174     };
175 
onInputDeviceAdded(JNIEnv *,jclass,jlong qtNativePtr,int deviceId)176     void onInputDeviceAdded(JNIEnv *, jclass, jlong qtNativePtr, int deviceId)
177     {
178         if (!qtNativePtr)
179             return;
180         reinterpret_cast<QAndroidGamepadBackend*>(qtNativePtr)->addDevice(deviceId);
181     }
onInputDeviceRemoved(JNIEnv *,jclass,jlong qtNativePtr,int deviceId)182     void onInputDeviceRemoved(JNIEnv *, jclass, jlong qtNativePtr, int deviceId)
183     {
184         if (!qtNativePtr)
185             return;
186         reinterpret_cast<QAndroidGamepadBackend*>(qtNativePtr)->removeDevice(deviceId);
187     }
onInputDeviceChanged(JNIEnv *,jclass,jlong qtNativePtr,int deviceId)188     void onInputDeviceChanged(JNIEnv *, jclass, jlong qtNativePtr, int deviceId)
189     {
190         if (!qtNativePtr)
191             return;
192         reinterpret_cast<QAndroidGamepadBackend*>(qtNativePtr)->updateDevice(deviceId);
193     }
194 
195     static JNINativeMethod methods[] = {
196         {"onInputDeviceAdded", "(JI)V", (void *)onInputDeviceAdded},
197         {"onInputDeviceRemoved", "(JI)V", (void *)onInputDeviceRemoved},
198         {"onInputDeviceChanged", "(JI)V", (void *)onInputDeviceChanged}
199     };
200 
201     const char qtGamePadClassName[] = "org/qtproject/qt5/android/gamepad/QtGamepad";
202 
setAxisInfo(QJNIObjectPrivate & event,int axis,QAndroidGamepadBackend::Mapping::AndroidAxisInfo & info)203     inline void setAxisInfo(QJNIObjectPrivate &event, int axis, QAndroidGamepadBackend::Mapping::AndroidAxisInfo &info)
204     {
205         QJNIObjectPrivate device(event.callObjectMethod("getDevice", "()Landroid/view/InputDevice;"));
206         if (device.isValid()) {
207             const int source = event.callMethod<jint>("getSource", "()I");
208             QJNIObjectPrivate motionRange = device.callObjectMethod("getMotionRange","(II)Landroid/view/InputDevice$MotionRange;", axis, source);
209             if (motionRange.isValid()) {
210                 info.flatArea = motionRange.callMethod<jfloat>("getFlat", "()F");
211                 info.minValue = motionRange.callMethod<jfloat>("getMin", "()F");
212                 info.maxValue = motionRange.callMethod<jfloat>("getMax", "()F");
213                 info.fuzz = motionRange.callMethod<jfloat>("getFuzz", "()F");
214                 return;
215             }
216         }
217         info.flatArea = 0;
218     }
219 
220 } // namespace
221 
Q_GLOBAL_STATIC(DefaultMapping,g_defaultMapping)222 Q_GLOBAL_STATIC(DefaultMapping, g_defaultMapping)
223 
224 void QAndroidGamepadBackend::Mapping::AndroidAxisInfo::restoreSavedData(const QVariantMap &value)
225 {
226     gamepadAxis = QGamepadManager::GamepadAxis(value[QLatin1String("axis")].toInt());
227     gamepadMinButton = QGamepadManager::GamepadButton(value[QLatin1String("minButton")].toInt());
228     gamepadMaxButton = QGamepadManager::GamepadButton(value[QLatin1String("maxButton")].toInt());
229 }
230 
dataToSave() const231 QVariantMap QAndroidGamepadBackend::Mapping::AndroidAxisInfo::dataToSave() const
232 {
233     QVariantMap data;
234     data[QLatin1String("axis")] = gamepadAxis;
235     data[QLatin1String("minButton")] = gamepadMinButton;
236     data[QLatin1String("maxButton")] = gamepadMaxButton;
237     return data;
238 }
239 
QAndroidGamepadBackend(QObject * parent)240 QAndroidGamepadBackend::QAndroidGamepadBackend(QObject *parent)
241     : QGamepadBackend(parent)
242 {
243     QtAndroidPrivate::registerGenericMotionEventListener(this);
244     QtAndroidPrivate::registerKeyEventListener(this);
245 }
246 
~QAndroidGamepadBackend()247 QAndroidGamepadBackend::~QAndroidGamepadBackend()
248 {
249     QtAndroidPrivate::unregisterGenericMotionEventListener(this);
250     QtAndroidPrivate::unregisterKeyEventListener(this);
251 }
252 
addDevice(int deviceId)253 void QAndroidGamepadBackend::addDevice(int deviceId)
254 {
255     if (deviceId == -1)
256         return;
257 
258     QMutexLocker lock(&m_mutex);
259     QJNIObjectPrivate inputDevice = QJNIObjectPrivate::callStaticObjectMethod(inputDeviceClass, "getDevice", "(I)Landroid/view/InputDevice;", deviceId);
260     int sources = inputDevice.callMethod<jint>("getSources", "()I");
261     bool acceptable = false;
262     for (int source : g_defaultMapping()->acceptedSources) {
263         if ( (source & sources) == source) {
264             acceptable = true;
265             break;
266         }
267     }
268 
269     if (acceptable) {
270         m_devices.insert(deviceId, *g_defaultMapping());
271         int productId = qHash(inputDevice.callObjectMethod("getDescriptor", "()Ljava/lang/String;").toString());
272         m_devices[deviceId].productId = productId;
273         if (productId) {
274             QVariant settings = readSettings(productId);
275             if (!settings.isNull()) {
276                 auto &deviceInfo = m_devices[deviceId];
277                 deviceInfo.needsConfigure = false;
278 
279                 QVariantMap data = settings.toMap()[AXES_KEY].toMap();
280                 for (QVariantMap::const_iterator it = data.begin(); it != data.end(); ++it)
281                     deviceInfo.axisMap[it.key().toInt()].restoreSavedData(it.value().toMap());
282 
283                 data = settings.toMap()[BUTTONS_KEY].toMap();
284                 for (QVariantMap::const_iterator it = data.begin(); it != data.end(); ++it)
285                     deviceInfo.buttonsMap[it.key().toInt()] = QGamepadManager::GamepadButton(it.value().toInt());
286             }
287         }
288         FunctionEvent::runOnQtThread(this, [this, deviceId]{
289             emit gamepadAdded(deviceId);
290         });
291     }
292 }
293 
updateDevice(int deviceId)294 void QAndroidGamepadBackend::updateDevice(int deviceId)
295 {
296     Q_UNUSED(deviceId)
297     //QMutexLocker lock(&m_mutex);
298 }
299 
removeDevice(int deviceId)300 void QAndroidGamepadBackend::removeDevice(int deviceId)
301 {
302     QMutexLocker lock(&m_mutex);
303     if (m_devices.remove(deviceId)) {
304         FunctionEvent::runOnQtThread(this, [this, deviceId]{
305             emit gamepadRemoved(deviceId);
306         });
307     }
308 }
309 
event(QEvent * ev)310 bool QAndroidGamepadBackend::event(QEvent *ev)
311 {
312     if (ev->type() == QEvent::User) {
313         static_cast<FunctionEvent*>(ev)->call();
314         return true;
315     }
316     return QGamepadBackend::event(ev);
317 }
318 
isConfigurationNeeded(int deviceId)319 bool QAndroidGamepadBackend::isConfigurationNeeded(int deviceId)
320 {
321     QMutexLocker lock(&m_mutex);
322     auto it = m_devices.find(deviceId);
323     if (it == m_devices.end())
324         return false;
325     return it->needsConfigure;
326 }
327 
configureButton(int deviceId,QGamepadManager::GamepadButton button)328 bool QAndroidGamepadBackend::configureButton(int deviceId, QGamepadManager::GamepadButton button)
329 {
330     QMutexLocker lock(&m_mutex);
331     auto it = m_devices.find(deviceId);
332     if (it == m_devices.end())
333         return false;
334 
335     it.value().calibrateButton = button;
336     return true;
337 }
338 
configureAxis(int deviceId,QGamepadManager::GamepadAxis axis)339 bool QAndroidGamepadBackend::configureAxis(int deviceId, QGamepadManager::GamepadAxis axis)
340 {
341     QMutexLocker lock(&m_mutex);
342     auto it = m_devices.find(deviceId);
343     if (it == m_devices.end())
344         return false;
345 
346     it.value().calibrateAxis = axis;
347     return true;
348 }
349 
setCancelConfigureButton(int deviceId,QGamepadManager::GamepadButton button)350 bool QAndroidGamepadBackend::setCancelConfigureButton(int deviceId, QGamepadManager::GamepadButton button)
351 {
352     QMutexLocker lock(&m_mutex);
353     auto it = m_devices.find(deviceId);
354     if (it == m_devices.end())
355         return false;
356 
357     it.value().cancelConfigurationButton = button;
358     return true;
359 }
360 
resetConfiguration(int deviceId)361 void QAndroidGamepadBackend::resetConfiguration(int deviceId)
362 {
363     QMutexLocker lock(&m_mutex);
364     auto it = m_devices.find(deviceId);
365     if (it == m_devices.end())
366         return;
367 
368     it.value().axisMap.clear();
369     it.value().buttonsMap.clear();
370     it.value().calibrateButton = QGamepadManager::ButtonInvalid;
371     it.value().calibrateAxis = QGamepadManager::AxisInvalid;
372     it.value().cancelConfigurationButton = QGamepadManager::ButtonInvalid;
373     it.value().needsConfigure = false;
374 }
375 
handleKeyEvent(jobject event)376 bool QAndroidGamepadBackend::handleKeyEvent(jobject event)
377 {
378     QJNIObjectPrivate ev(event);
379     QMutexLocker lock(&m_mutex);
380     const int deviceId = ev.callMethod<jint>("getDeviceId", "()I");
381     const auto deviceIt = m_devices.find(deviceId);
382     if (deviceIt == m_devices.end())
383         return false;
384 
385     const int action = ev.callMethod<jint>("getAction", "()I");
386     if (action != g_defaultMapping()->ACTION_DOWN &&
387             action != g_defaultMapping()->ACTION_UP) {
388         return false;
389     }
390     const int flags = ev.callMethod<jint>("getFlags", "()I");
391     if ((flags & g_defaultMapping()->FLAG_LONG_PRESS) == g_defaultMapping()->FLAG_LONG_PRESS)
392         return false;
393 
394     const int keyCode = ev.callMethod<jint>("getKeyCode", "()I");
395     auto &deviceMap = deviceIt.value();
396 
397     if (deviceMap.cancelConfigurationButton != QGamepadManager::ButtonInvalid &&
398             (deviceMap.calibrateButton != QGamepadManager::ButtonInvalid ||
399              deviceMap.calibrateAxis != QGamepadManager::AxisInvalid)) {
400 
401         const auto buttonsMapIt = deviceMap.buttonsMap.find(keyCode);
402         if (buttonsMapIt != deviceMap.buttonsMap.end() &&
403                 deviceMap.cancelConfigurationButton == buttonsMapIt.value()) {
404             deviceMap.calibrateButton = QGamepadManager::ButtonInvalid;
405             deviceMap.calibrateAxis = QGamepadManager::AxisInvalid;
406             FunctionEvent::runOnQtThread(this, [this, deviceId]{
407                 emit configurationCanceled(deviceId);
408             });
409             return true;
410         }
411     }
412 
413     if (deviceMap.calibrateButton != QGamepadManager::ButtonInvalid) {
414         deviceMap.buttonsMap[keyCode] = deviceMap.calibrateButton;
415         auto but = deviceMap.calibrateButton;
416         deviceMap.calibrateButton = QGamepadManager::ButtonInvalid;
417         saveData(deviceMap);
418         FunctionEvent::runOnQtThread(this, [this, deviceId, but]{
419             emit buttonConfigured(deviceId, but);
420         });
421     }
422 
423     const auto buttonsMapIt = deviceMap.buttonsMap.find(keyCode);
424     if (buttonsMapIt == deviceMap.buttonsMap.end())
425         return false;
426 
427     const auto button = buttonsMapIt.value();
428     if (action == g_defaultMapping()->ACTION_DOWN) {
429         FunctionEvent::runOnQtThread(this, [this, deviceId, button]{
430             emit gamepadButtonPressed(deviceId, button, 1.0);
431         });
432     } else {
433         FunctionEvent::runOnQtThread(this, [this, deviceId, button]{
434            emit gamepadButtonReleased(deviceId, button);
435         });
436     }
437     return true;
438 }
439 
handleGenericMotionEvent(jobject event)440 bool QAndroidGamepadBackend::handleGenericMotionEvent(jobject event)
441 {
442     // GenericMotionEvent was introduced in API-12
443     Q_ASSERT(QtAndroidPrivate::androidSdkVersion() >= 12);
444 
445     QJNIObjectPrivate ev(event);
446     QMutexLocker lock(&m_mutex);
447     const int deviceId = ev.callMethod<jint>("getDeviceId", "()I");
448     const auto deviceIt = m_devices.find(deviceId);
449     if (deviceIt == m_devices.end())
450         return false;
451 
452     auto &deviceMap = deviceIt.value();
453     if (deviceMap.calibrateAxis != QGamepadManager::AxisInvalid ||
454             deviceMap.calibrateButton != QGamepadManager::ButtonInvalid) {
455         double lastValue = 0;
456         int lastAxis = -1;
457         for (int axis : g_defaultMapping()->allAndroidAxes) {
458             double value = ev.callMethod<jfloat>("getAxisValue", "(I)F", axis);
459             if (fabs(value) > fabs(lastValue)) {
460                 lastValue = value;
461                 lastAxis = axis;
462             }
463         }
464 
465         if (!lastValue || lastAxis == -1)
466             return false;
467 
468         if (deviceMap.calibrateAxis != QGamepadManager::AxisInvalid) {
469             deviceMap.axisMap[lastAxis].gamepadAxis = deviceMap.calibrateAxis;
470             auto axis = deviceMap.calibrateAxis;
471             deviceMap.calibrateAxis = QGamepadManager::AxisInvalid;
472             saveData(deviceMap);
473             FunctionEvent::runOnQtThread(this, [this, deviceId, axis]{
474                 emit axisConfigured(deviceId, axis);
475             });
476         } else if (deviceMap.calibrateButton != QGamepadManager::ButtonInvalid &&
477                    deviceMap.calibrateButton != QGamepadManager::ButtonUp &&
478                    deviceMap.calibrateButton != QGamepadManager::ButtonDown &&
479                    deviceMap.calibrateButton != QGamepadManager::ButtonLeft &&
480                    deviceMap.calibrateButton != QGamepadManager::ButtonRight) {
481             auto &axis = deviceMap.axisMap[lastAxis];
482             axis.gamepadAxis = QGamepadManager::AxisInvalid;
483             setAxisInfo(ev, lastAxis, axis);
484             bool save = false;
485             if (lastValue == axis.minValue) {
486                 axis.gamepadMinButton = deviceMap.calibrateButton;
487                 if (axis.gamepadMaxButton == QGamepadManager::ButtonInvalid)
488                     axis.gamepadMaxButton = deviceMap.calibrateButton;
489                 save = true;
490             } else if (lastValue == axis.maxValue) {
491                 axis.gamepadMaxButton = deviceMap.calibrateButton;
492                 if (axis.gamepadMinButton == QGamepadManager::ButtonInvalid)
493                     axis.gamepadMinButton = deviceMap.calibrateButton;
494                 save = true;
495             }
496 
497             if (save) {
498                 auto but = deviceMap.calibrateButton;
499                 deviceMap.calibrateButton = QGamepadManager::ButtonInvalid;
500                 saveData(deviceMap);
501                 FunctionEvent::runOnQtThread(this, [this, deviceId, but]{
502                     emit buttonConfigured(deviceId, but);
503                 });
504             }
505         }
506     }
507 
508     typedef QPair<QGamepadManager::GamepadAxis, double> GamepadAxisValue;
509     QVector<GamepadAxisValue> axisValues;
510     typedef QPair<QGamepadManager::GamepadButton, double> GamepadButtonValue;
511     QVector<GamepadButtonValue> buttonValues;
512     auto setValue = [&axisValues, &buttonValues](Mapping::AndroidAxisInfo &axisInfo, double value) {
513         if (axisInfo.setValue(value)) {
514             if (axisInfo.gamepadAxis != QGamepadManager::AxisInvalid) {
515                 axisValues.push_back(GamepadAxisValue(axisInfo.gamepadAxis, axisInfo.lastValue));
516             } else {
517                 if (axisInfo.lastValue < 0) {
518                     buttonValues.push_back(GamepadButtonValue(axisInfo.gamepadMinButton, axisInfo.lastValue));
519                     axisInfo.gamepadLastButton = axisInfo.gamepadMinButton;
520                 } else if (axisInfo.lastValue > 0) {
521                     buttonValues.push_back(GamepadButtonValue(axisInfo.gamepadMaxButton, axisInfo.lastValue));
522                     axisInfo.gamepadLastButton = axisInfo.gamepadMaxButton;
523                 } else {
524                     buttonValues.push_back(GamepadButtonValue(axisInfo.gamepadLastButton, 0.0));
525                     axisInfo.gamepadLastButton = QGamepadManager::ButtonInvalid;
526                 }
527             }
528         }
529     };
530     for (auto it = deviceMap.axisMap.begin(); it != deviceMap.axisMap.end(); ++it) {
531         auto &axisInfo = it.value();
532         if (axisInfo.flatArea == -1)
533             setAxisInfo(ev, it.key(), axisInfo);
534         const int historicalValues = ev.callMethod<jint>("getHistorySize", "()I");
535         for (int i = 0; i < historicalValues; ++i) {
536             double value = ev.callMethod<jfloat>("getHistoricalAxisValue", "(II)F", it.key(), i);
537             setValue(axisInfo, value);
538         }
539         double value = ev.callMethod<jfloat>("getAxisValue", "(I)F", it.key());
540         setValue(axisInfo, value);
541     }
542 
543     if (!axisValues.isEmpty()) {
544         FunctionEvent::runOnQtThread(this, [this, deviceId, axisValues]{
545             for (const auto &axisValue : axisValues)
546                 emit gamepadAxisMoved(deviceId, axisValue.first, axisValue.second);
547         });
548     }
549 
550     if (!buttonValues.isEmpty()) {
551         FunctionEvent::runOnQtThread(this, [this, deviceId, buttonValues]{
552             for (const auto &buttonValue : buttonValues)
553                 if (buttonValue.second)
554                     emit gamepadButtonPressed(deviceId, buttonValue.first, fabs(buttonValue.second));
555                 else
556                     emit gamepadButtonReleased(deviceId, buttonValue.first);
557         });
558     }
559 
560     return false;
561 }
562 
start()563 bool QAndroidGamepadBackend::start()
564 {
565     {
566         QMutexLocker lock(&m_mutex);
567         if (QtAndroidPrivate::androidSdkVersion() >= 16) {
568             if (!m_qtGamepad.isValid())
569                 m_qtGamepad = QJNIObjectPrivate(qtGamePadClassName, "(Landroid/app/Activity;)V", QtAndroidPrivate::activity());
570             m_qtGamepad.callMethod<void>("register", "(J)V", jlong(this));
571         }
572     }
573 
574     QJNIObjectPrivate ids = QJNIObjectPrivate::callStaticObjectMethod(inputDeviceClass, "getDeviceIds", "()[I");
575     jintArray jarr = jintArray(ids.object());
576     QJNIEnvironmentPrivate env;
577     size_t sz = env->GetArrayLength(jarr);
578     jint *buff = env->GetIntArrayElements(jarr, nullptr);
579     for (size_t i = 0; i < sz; ++i)
580         addDevice(buff[i]);
581     env->ReleaseIntArrayElements(jarr, buff, 0);
582     return true;
583 }
584 
stop()585 void QAndroidGamepadBackend::stop()
586 {
587     QMutexLocker lock(&m_mutex);
588     if (QtAndroidPrivate::androidSdkVersion() >= 16 && m_qtGamepad.isValid())
589         m_qtGamepad.callMethod<void>("unregister", "()V");
590 }
591 
saveData(const QAndroidGamepadBackend::Mapping & deviceInfo)592 void QAndroidGamepadBackend::saveData(const QAndroidGamepadBackend::Mapping &deviceInfo)
593 {
594     if (!deviceInfo.productId)
595         return ;
596 
597     QVariantMap settings, data;
598     for (auto it = deviceInfo.axisMap.begin(); it != deviceInfo.axisMap.end(); ++it)
599         data[QString::number(it.key())] = it.value().dataToSave();
600     settings[AXES_KEY] = data;
601 
602     data.clear();
603     for (auto it = deviceInfo.buttonsMap.begin(); it != deviceInfo.buttonsMap.end(); ++it)
604         data[QString::number(it.key())] = it.value();
605     settings[BUTTONS_KEY] = data;
606 
607     saveSettings(deviceInfo.productId, settings);
608 }
609 
JNI_OnLoad(JavaVM * vm,void *)610 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
611 {
612     static bool initialized = false;
613     if (initialized)
614         return JNI_VERSION_1_6;
615     initialized = true;
616 
617     JNIEnv* env;
618     // get the JNIEnv pointer.
619     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
620         return JNI_ERR;
621 
622     // search for Java class which declares the native methods
623     jclass javaClass = env->FindClass("org/qtproject/qt5/android/gamepad/QtGamepad");
624     if (!javaClass)
625         return JNI_ERR;
626 
627     // register our native methods
628     if (env->RegisterNatives(javaClass, methods,
629                              sizeof(methods) / sizeof(methods[0])) < 0) {
630         return JNI_ERR;
631     }
632     return JNI_VERSION_1_6;
633 }
634 
635 QT_END_NAMESPACE
636