JoystickAndroid.cc 10.8 KB
Newer Older
1 2 3 4 5 6 7 8
#include "JoystickAndroid.h"

#include "QGCApplication.h"

#include <QQmlEngine>

int JoystickAndroid::_androidBtnListCount;
int *JoystickAndroid::_androidBtnList;
Gregory Dymarek's avatar
Gregory Dymarek committed
9 10
int JoystickAndroid::ACTION_DOWN;
int JoystickAndroid::ACTION_UP;
11 12
QMutex JoystickAndroid::m_mutex;

13 14 15 16 17 18 19 20 21
static void clear_jni_exception()
{
    QAndroidJniEnvironment jniEnv;
    if (jniEnv->ExceptionCheck()) {
        jniEnv->ExceptionDescribe();
        jniEnv->ExceptionClear();
    }
}

Gregory Dymarek's avatar
Gregory Dymarek committed
22
JoystickAndroid::JoystickAndroid(const QString& name, int axisCount, int buttonCount, int id, MultiVehicleManager* multiVehicleManager)
Gregory Dymarek's avatar
Gregory Dymarek committed
23
    : Joystick(name,axisCount,buttonCount,0,multiVehicleManager)
24 25 26 27 28
    , deviceId(id)
{
    int i;
    
    QAndroidJniEnvironment env;
Gregory Dymarek's avatar
Gregory Dymarek committed
29
    QAndroidJniObject inputDevice = QAndroidJniObject::callStaticObjectMethod("android/view/InputDevice", "getDevice", "(I)Landroid/view/InputDevice;", id);
30

31 32 33 34 35
    //set button mapping (number->code)
    jintArray b = env->NewIntArray(_androidBtnListCount);
    env->SetIntArrayRegion(b,0,_androidBtnListCount,_androidBtnList);

    QAndroidJniObject btns = inputDevice.callObjectMethod("hasKeys", "([I)[Z", b);
36 37 38 39 40 41
    jbooleanArray jSupportedButtons = btns.object<jbooleanArray>();
    jboolean* supportedButtons = env->GetBooleanArrayElements(jSupportedButtons, nullptr);
    //create a mapping table (btnCode) that maps button number with button code
    btnValue = new bool[_buttonCount];
    btnCode = new int[_buttonCount];
    int c = 0;
42
    for (i = 0; i < _androidBtnListCount; i++) {
43
        if (supportedButtons[i]) {
44 45 46 47
            btnValue[c] = false;
            btnCode[c] = _androidBtnList[i];
            c++;
        }
48
    }
49 50 51

    env->ReleaseBooleanArrayElements(jSupportedButtons, supportedButtons, 0);

52
    // set axis mapping (number->code)
53 54
    axisValue = new int[_axisCount];
    axisCode = new int[_axisCount];
55
    QAndroidJniObject rangeListNative = inputDevice.callObjectMethod("getMotionRanges", "()Ljava/util/List;");
56
    for (i = 0; i < _axisCount; i++) {
Gregory Dymarek's avatar
Gregory Dymarek committed
57 58
        QAndroidJniObject range = rangeListNative.callObjectMethod("get", "(I)Ljava/lang/Object;",i);
        axisCode[i] = range.callMethod<jint>("getAxis");
59 60 61 62 63 64 65
        // Don't allow two axis with the same code
        for (int j = 0; j < i; j++) {
            if (axisCode[i] == axisCode[j]) {
                axisCode[i] = -1;
                break;
            }
        }
Gregory Dymarek's avatar
Gregory Dymarek committed
66
        axisValue[i] = 0;
67 68
    }

Gregory Dymarek's avatar
Gregory Dymarek committed
69
    qCDebug(JoystickLog) << "axis:" <<_axisCount << "buttons:" <<_buttonCount;
70 71 72 73 74 75 76 77 78 79 80 81 82 83
    QtAndroidPrivate::registerGenericMotionEventListener(this);
    QtAndroidPrivate::registerKeyEventListener(this);
}

JoystickAndroid::~JoystickAndroid() {
    delete btnCode;
    delete axisCode;
    delete btnValue;
    delete axisValue;

    QtAndroidPrivate::unregisterGenericMotionEventListener(this);
    QtAndroidPrivate::unregisterKeyEventListener(this);
}

84

85 86 87 88 89 90 91 92
QMap<QString, Joystick*> JoystickAndroid::discover(MultiVehicleManager* _multiVehicleManager) {
    static QMap<QString, Joystick*> ret;

    QMutexLocker lock(&m_mutex);

    QAndroidJniEnvironment env;
    QAndroidJniObject o = QAndroidJniObject::callStaticObjectMethod<jintArray>("android/view/InputDevice", "getDeviceIds");
    jintArray jarr = o.object<jintArray>();
93
    int sz = env->GetArrayLength(jarr);
94 95 96 97 98
    jint *buff = env->GetIntArrayElements(jarr, nullptr);

    int SOURCE_GAMEPAD = QAndroidJniObject::getStaticField<jint>("android/view/InputDevice", "SOURCE_GAMEPAD");
    int SOURCE_JOYSTICK = QAndroidJniObject::getStaticField<jint>("android/view/InputDevice", "SOURCE_JOYSTICK");

99 100
    QList<QString> names;

101
    for (int i = 0; i < sz; ++i) {
102 103 104 105 106
        QAndroidJniObject inputDevice = QAndroidJniObject::callStaticObjectMethod("android/view/InputDevice", "getDevice", "(I)Landroid/view/InputDevice;", buff[i]);
        int sources = inputDevice.callMethod<jint>("getSources", "()I");
        if (((sources & SOURCE_GAMEPAD) != SOURCE_GAMEPAD) //check if the input device is interesting to us
                && ((sources & SOURCE_JOYSTICK) != SOURCE_JOYSTICK)) continue;

107
        // get id and name
108 109 110
        QString id = inputDevice.callObjectMethod("getDescriptor", "()Ljava/lang/String;").toString();
        QString name = inputDevice.callObjectMethod("getName", "()Ljava/lang/String;").toString();

111
        names.push_back(name);
112

113
        if (ret.contains(name)) {
114 115 116
            continue;
        }

117
        // get number of axis
118 119 120
        QAndroidJniObject rangeListNative = inputDevice.callObjectMethod("getMotionRanges", "()Ljava/util/List;");
        int axisCount = rangeListNative.callMethod<jint>("size");

121
        // get number of buttons
122 123 124 125 126 127
        jintArray a = env->NewIntArray(_androidBtnListCount);
        env->SetIntArrayRegion(a,0,_androidBtnListCount,_androidBtnList);
        QAndroidJniObject btns = inputDevice.callObjectMethod("hasKeys", "([I)[Z", a);
        jbooleanArray jSupportedButtons = btns.object<jbooleanArray>();
        jboolean* supportedButtons = env->GetBooleanArrayElements(jSupportedButtons, nullptr);
        int buttonCount = 0;
Gregory Dymarek's avatar
Gregory Dymarek committed
128 129
        for (int j=0;j<_androidBtnListCount;j++)
            if (supportedButtons[j]) buttonCount++;
130 131
        env->ReleaseBooleanArrayElements(jSupportedButtons, supportedButtons, 0);

Gregory Dymarek's avatar
Gregory Dymarek committed
132
        qCDebug(JoystickLog) << "\t" << name << "id:" << buff[i] << "axes:" << axisCount << "buttons:" << buttonCount;
133

Gregory Dymarek's avatar
Gregory Dymarek committed
134
        ret[name] = new JoystickAndroid(name, axisCount, buttonCount, buff[i], _multiVehicleManager);
135 136 137 138 139 140 141 142
    }

    for (auto i = ret.begin(); i != ret.end();) {
        if (!names.contains(i.key())) {
            i = ret.erase(i);
        } else {
            i++;
        }
143 144 145 146 147 148 149
    }

    env->ReleaseIntArrayElements(jarr, buff, 0);

    return ret;
}

Gregory Dymarek's avatar
Gregory Dymarek committed
150 151 152 153 154 155 156 157 158 159

bool JoystickAndroid::handleKeyEvent(jobject event) {
    QJNIObjectPrivate ev(event);
    QMutexLocker lock(&m_mutex);
    const int _deviceId = ev.callMethod<jint>("getDeviceId", "()I");
    if (_deviceId!=deviceId) return false;
 
    const int action = ev.callMethod<jint>("getAction", "()I");
    const int keyCode = ev.callMethod<jint>("getKeyCode", "()I");

160 161 162 163
    for (int i = 0; i <_buttonCount; i++) {
        if (btnCode[i] == keyCode) {
            if (action == ACTION_DOWN) btnValue[i] = true;
            if (action == ACTION_UP)   btnValue[i] = false;
Gregory Dymarek's avatar
Gregory Dymarek committed
164 165 166 167 168 169 170 171 172 173 174 175
            return true;
        }
    }
    return false;
}

bool JoystickAndroid::handleGenericMotionEvent(jobject event) {
    QJNIObjectPrivate ev(event);
    QMutexLocker lock(&m_mutex);
    const int _deviceId = ev.callMethod<jint>("getDeviceId", "()I");
    if (_deviceId!=deviceId) return false;
 
176
    for (int i = 0; i <_axisCount; i++) {
Gregory Dymarek's avatar
Gregory Dymarek committed
177
        const float v = ev.callMethod<jfloat>("getAxisValue", "(I)F",axisCode[i]);
178
        axisValue[i] = static_cast<int>((v*32767.f));
Gregory Dymarek's avatar
Gregory Dymarek committed
179 180 181 182
    }
    return true;
}

Gregory Dymarek's avatar
Gregory Dymarek committed
183
bool JoystickAndroid::_open(void) {
184 185 186
    return true;
}

Gregory Dymarek's avatar
Gregory Dymarek committed
187
void JoystickAndroid::_close(void) {
188 189
}

Gregory Dymarek's avatar
Gregory Dymarek committed
190
bool JoystickAndroid::_update(void)
191 192 193 194
{
    return true;
}

Gregory Dymarek's avatar
Gregory Dymarek committed
195
bool JoystickAndroid::_getButton(int i) {
Gregory Dymarek's avatar
Gregory Dymarek committed
196
    return btnValue[ i ];
197 198
}

Gregory Dymarek's avatar
Gregory Dymarek committed
199
int JoystickAndroid::_getAxis(int i) {
Gregory Dymarek's avatar
Gregory Dymarek committed
200
    return axisValue[ i ];
201 202
}

Gregory Dymarek's avatar
Gregory Dymarek committed
203 204 205 206 207 208
uint8_t JoystickAndroid::_getHat(int hat,int i) {
    Q_UNUSED(hat);
    Q_UNUSED(i);

    return 0;
}
209

210 211
static JoystickManager *_manager = nullptr;

212
//helper method
213 214 215 216 217
bool JoystickAndroid::init(JoystickManager *manager) {
    if (_manager == nullptr) {
        setNativeMethods(manager);
    }

218 219 220 221 222 223 224 225 226 227
    //this gets list of all possible buttons - this is needed to check how many buttons our gamepad supports
    //instead of the whole logic below we could have just a simple array of hardcoded int values as these 'should' not change

    //int JoystickAndroid::_androidBtnListCount;
    _androidBtnListCount = 31;
    static int ret[31]; //there are 31 buttons in total accordingy to the API
    int i;
    //int *JoystickAndroid::
    _androidBtnList = ret;

228 229
    clear_jni_exception();
    for (i = 1; i <= 16; i++) {
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
        QString name = "KEYCODE_BUTTON_"+QString::number(i);
        ret[i-1] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", name.toStdString().c_str());
    }
    i--;

    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_A");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_B");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_C");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_L1");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_L2");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_R1");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_R2");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_MODE");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_SELECT");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_START");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_THUMBL");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_THUMBR");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_X");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_Y");
    ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_Z");

Gregory Dymarek's avatar
Gregory Dymarek committed
251 252
    ACTION_DOWN = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "ACTION_DOWN");
    ACTION_UP = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "ACTION_UP");
253 254

    return true;
255 256
}

257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
static const char kJniClassName[] {"org/mavlink/qgroundcontrol/QGCActivity"};

static void jniUpdateAvailableJoysticks(JNIEnv *envA, jobject thizA)
{
    Q_UNUSED(envA);
    Q_UNUSED(thizA);

    if (_manager != nullptr) {
        qCDebug(JoystickLog) << "jniUpdateAvailableJoysticks triggered";
        emit _manager->updateAvailableJoysticksSignal();
    }
}

void JoystickAndroid::setNativeMethods(JoystickManager *manager)
{
    qCDebug(JoystickLog) << "Registering Native Functions";

    _manager = manager;

    //  REGISTER THE C++ FUNCTION WITH JNI
    JNINativeMethod javaMethods[] {
        {"nativeUpdateAvailableJoysticks", "()V", reinterpret_cast<void *>(jniUpdateAvailableJoysticks)}
    };

281
    clear_jni_exception();
282 283 284
    QAndroidJniEnvironment jniEnv;
    jclass objectClass = jniEnv->FindClass(kJniClassName);
    if(!objectClass) {
285
        clear_jni_exception();
286 287 288 289 290 291 292 293 294 295 296
        qWarning() << "Couldn't find class:" << kJniClassName;
        return;
    }

    jint val = jniEnv->RegisterNatives(objectClass, javaMethods, sizeof(javaMethods) / sizeof(javaMethods[0]));

    if (val < 0) {
        qWarning() << "Error registering methods: " << val;
    } else {
        qCDebug(JoystickLog) << "Native Functions Registered";
    }
297
    clear_jni_exception();
298
}