/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "GameControllerManager.h" #include #include #include #include "GameControllerInternalConstants.h" #include "GameControllerLog.h" #include "GameControllerMappingUtils.h" #include "InternalControllerTable.h" #include "Log.h" #define ARRAY_COUNTOF(array) (sizeof(array) / sizeof(array[0])) #define LOG_TAG "GameControllerManager" // If defined, output information about qualifying game controller // event data to log //#define LOG_INPUT_EVENTS // JNI interface functions extern "C" { void Java_com_google_android_games_paddleboat_GameControllerManager_onControllerConnected( JNIEnv *env, jobject gcmObject, jintArray deviceInfoArray, jfloatArray axisMinArray, jfloatArray axisMaxArray, jfloatArray axisFlatArray, jfloatArray axisFuzzArray) { paddleboat::GameControllerDeviceInfo *deviceInfo = paddleboat::GameControllerManager::onConnection(); if (deviceInfo != nullptr) { // Copy the array contents into the DeviceInfo array equivalents const jsize infoArraySize = env->GetArrayLength(deviceInfoArray); if ((infoArraySize * sizeof(int32_t)) == sizeof(paddleboat::GameControllerDeviceInfo::InfoFields)) { env->GetIntArrayRegion( deviceInfoArray, 0, infoArraySize, reinterpret_cast(deviceInfo->getInfo())); } else { ALOGE( "deviceInfoArray/GameControllerDeviceInfo::InfoFields size " "mismatch"); } #if defined LOG_INPUT_EVENTS ALOGI("onControllerConnected deviceId %d", deviceInfo->getInfo()->mDeviceId); #endif // all axis arrays are presumed to be the same size const jsize axisArraySize = env->GetArrayLength(axisMinArray); if ((axisArraySize * sizeof(float)) == (sizeof(float) * paddleboat::MAX_AXIS_COUNT)) { env->GetFloatArrayRegion(axisMinArray, 0, axisArraySize, deviceInfo->getMinArray()); env->GetFloatArrayRegion(axisMaxArray, 0, axisArraySize, deviceInfo->getMaxArray()); env->GetFloatArrayRegion(axisFlatArray, 0, axisArraySize, deviceInfo->getFlatArray()); env->GetFloatArrayRegion(axisFuzzArray, 0, axisArraySize, deviceInfo->getFuzzArray()); } else { ALOGE( "axisArray/GameControllerDeviceInfo::axisArray size mismatch"); } // Retrieve the device name string const jint deviceId = deviceInfo->getInfo()->mDeviceId; jmethodID getDeviceNameById = env->GetMethodID( paddleboat::GameControllerManager::getGameControllerClass(), "getDeviceNameById", "(I)Ljava/lang/String;"); if (getDeviceNameById != NULL) { jstring deviceNameJstring = reinterpret_cast< jstring>(env->CallObjectMethod( paddleboat::GameControllerManager::getGameControllerObject(), getDeviceNameById, deviceId)); const char *deviceName = env->GetStringUTFChars(deviceNameJstring, NULL); if (deviceName != nullptr) { deviceInfo->setName(deviceName); } env->ReleaseStringUTFChars(deviceNameJstring, deviceName); } } } void Java_com_google_android_games_paddleboat_GameControllerManager_onControllerDisconnected( JNIEnv *env, jobject gcmObject, jint deviceId) { paddleboat::GameControllerManager::onDisconnection(deviceId); } void Java_com_google_android_games_paddleboat_GameControllerManager_onMotionData( JNIEnv *env, jobject gcmObject, jint deviceId, jint motionType, jlong timestamp, jfloat dataX, jfloat dataY, jfloat dataZ) { paddleboat::GameControllerManager::onMotionData( deviceId, motionType, timestamp, dataX, dataY, dataZ); } void Java_com_google_android_games_paddleboat_GameControllerManager_onMouseConnected( JNIEnv *env, jobject gcmObject, jint deviceId) { paddleboat::GameControllerManager::onMouseConnection(deviceId); } void Java_com_google_android_games_paddleboat_GameControllerManager_onMouseDisconnected( JNIEnv *env, jobject gcmObject, jint deviceId) { paddleboat::GameControllerManager::onMouseDisconnection(deviceId); } } // extern "C" namespace paddleboat { constexpr const char *CLASSLOADER_CLASS = "java/lang/ClassLoader"; constexpr const char *CLASSLOADER_GETCLASSLOADER_METHOD_NAME = "getClassLoader"; constexpr const char *CLASSLOADER_GETCLASSLOADER_METHOD_SIG = "()Ljava/lang/ClassLoader;"; constexpr const char *CLASSLOADER_LOADCLASS_METHOD_NAME = "loadClass"; constexpr const char *CLASSLOADER_LOADCLASS_METHOD_SIG = "(Ljava/lang/String;)Ljava/lang/Class;"; constexpr const char *GCM_CLASSNAME = "com/google/android/games/paddleboat/GameControllerManager"; constexpr const char *GCM_INIT_METHOD_NAME = ""; constexpr const char *GCM_INIT_METHOD_SIGNATURE = "(Landroid/content/Context;Z)V"; constexpr const char *GCM_ONSTOP_METHOD_NAME = "onStop"; constexpr const char *GCM_ONSTART_METHOD_NAME = "onStart"; constexpr const char *GCM_GETAPILEVEL_METHOD_NAME = "getApiLevel"; constexpr const char *GCM_GETAPILEVEL_METHOD_SIGNATURE = "()I"; constexpr const char *GCM_GETBATTERYLEVEL_METHOD_NAME = "getBatteryLevel"; constexpr const char *GCM_GETBATTERYLEVEL_METHOD_SIGNATURE = "(I)F"; constexpr const char *GCM_GETBATTERYSTATUS_METHOD_NAME = "getBatteryStatus"; constexpr const char *GCM_GETBATTERYSTATUS_METHOD_SIGNATURE = "(I)I"; constexpr const char *GCM_SETLIGHT_METHOD_NAME = "setLight"; constexpr const char *GCM_SETLIGHT_METHOD_SIGNATURE = "(III)V"; constexpr const char *GCM_SETNATIVEREADY_METHOD_NAME = "setNativeReady"; constexpr const char *GCM_SETREPORTMOTIONEVENTS_METHOD_NAME = "setReportMotionEvents"; constexpr const char *GCM_SETVIBRATION_METHOD_NAME = "setVibration"; constexpr const char *GCM_SETVIBRATION_METHOD_SIGNATURE = "(IIIII)V"; constexpr const char *VOID_METHOD_SIGNATURE = "()V"; constexpr float VIBRATION_INTENSITY_SCALE = 255.0f; typedef struct MethodTableEntry { const char *methodName; const char *methodSignature; jmethodID *methodID; } MethodTableEntry; const JNINativeMethod GCM_NATIVE_METHODS[] = { {"onControllerConnected", "([I[F[F[F[F)V", reinterpret_cast( Java_com_google_android_games_paddleboat_GameControllerManager_onControllerConnected)}, {"onControllerDisconnected", "(I)V", reinterpret_cast( Java_com_google_android_games_paddleboat_GameControllerManager_onControllerDisconnected)}, {"onMotionData", "(IIJFFF)V", reinterpret_cast( Java_com_google_android_games_paddleboat_GameControllerManager_onMotionData)}, {"onMouseConnected", "(I)V", reinterpret_cast( Java_com_google_android_games_paddleboat_GameControllerManager_onMouseConnected)}, {"onMouseDisconnected", "(I)V", reinterpret_cast( Java_com_google_android_games_paddleboat_GameControllerManager_onMouseDisconnected)}, }; std::mutex GameControllerManager::sInstanceMutex; std::unique_ptr GameControllerManager::sInstance; Paddleboat_ErrorCode GameControllerManager::init(JNIEnv *env, jobject jcontext) { std::lock_guard lock(sInstanceMutex); if (sInstance) { ALOGE("Attempted to initialize Paddleboat twice"); return PADDLEBOAT_ERROR_ALREADY_INITIALIZED; } sInstance = std::make_unique(env, jcontext, ConstructorTag{}); if (!sInstance->mInitialized) { ALOGE("Failed to initialize Paddleboat"); return PADDLEBOAT_ERROR_INIT_GCM_FAILURE; } GameControllerManager *gcm = sInstance.get(); if (gcm == nullptr) { ALOGE("Failed to initialize Paddleboat"); return PADDLEBOAT_ERROR_INIT_GCM_FAILURE; } // Load our GameControllerManager class jclass activityClass = env->GetObjectClass(jcontext); jmethodID getClassLoaderID = env->GetMethodID(activityClass, CLASSLOADER_GETCLASSLOADER_METHOD_NAME, CLASSLOADER_GETCLASSLOADER_METHOD_SIG); jobject classLoaderObject = env->CallObjectMethod(jcontext, getClassLoaderID); jclass classLoader = env->FindClass(CLASSLOADER_CLASS); jmethodID loadClassID = env->GetMethodID(classLoader, CLASSLOADER_LOADCLASS_METHOD_NAME, CLASSLOADER_LOADCLASS_METHOD_SIG); jstring gcmClassName = env->NewStringUTF(GCM_CLASSNAME); jclass gcmClass = jclass( env->CallObjectMethod(classLoaderObject, loadClassID, gcmClassName)); if (gcmClass == NULL) { ALOGE("Failed to find GameControllerManager class"); return PADDLEBOAT_ERROR_INIT_GCM_FAILURE; } // Register our native methods, in case we were linked as a static library int rc = env->RegisterNatives( gcmClass, GCM_NATIVE_METHODS, sizeof(GCM_NATIVE_METHODS) / sizeof(JNINativeMethod)); if (rc != JNI_OK) { ALOGE("Failed to register native methods. %d", rc); return PADDLEBOAT_ERROR_INIT_GCM_FAILURE; } jclass global_gcmClass = static_cast(env->NewGlobalRef(gcmClass)); // Retrieve our JNI methodIDs to the game controller manager class object gcm->mGameControllerClass = global_gcmClass; Paddleboat_ErrorCode methodResult = gcm->initMethods(env); if (methodResult != PADDLEBOAT_NO_ERROR) { return methodResult; } #if _DEBUG jboolean printControllerInfo = JNI_TRUE; #else jboolean printControllerInfo = JNI_FALSE; #endif jobject gcmObject = env->NewObject(global_gcmClass, gcm->mInitMethodId, jcontext, printControllerInfo); if (gcmObject == NULL) { ALOGE("Failed to create GameControllerManager"); return PADDLEBOAT_ERROR_INIT_GCM_FAILURE; } jobject global_gcmObject = env->NewGlobalRef(gcmObject); env->DeleteLocalRef(gcmObject); gcm->mGameControllerObject = global_gcmObject; return PADDLEBOAT_NO_ERROR; } void GameControllerManager::destroyInstance(JNIEnv *env) { std::lock_guard lock(sInstanceMutex); sInstance.get()->releaseGlobals(env); sInstance.reset(); } void GameControllerManager::releaseGlobals(JNIEnv *env) { if (mGameControllerClass != NULL) { env->DeleteGlobalRef(mGameControllerClass); mGameControllerClass = NULL; } if (mGameControllerObject != NULL) { env->DeleteGlobalRef(mGameControllerObject); mGameControllerObject = NULL; } } GameControllerManager *GameControllerManager::getInstance() { std::lock_guard lock(sInstanceMutex); return sInstance.get(); } bool GameControllerManager::isInitialized() { GameControllerManager *gcm = getInstance(); if (!gcm) { // This is a case of error. // We do not log anything here, so that we do not spam // the user when this function is called each frame. return false; } return gcm->mGCMClassInitialized; } Paddleboat_ErrorCode GameControllerManager::initMethods(JNIEnv *env) { const MethodTableEntry methodTable[] = { {GCM_INIT_METHOD_NAME, GCM_INIT_METHOD_SIGNATURE, &mInitMethodId}, {GCM_GETAPILEVEL_METHOD_NAME, GCM_GETAPILEVEL_METHOD_SIGNATURE, &mGetApiLevelMethodId}, {GCM_GETBATTERYLEVEL_METHOD_NAME, GCM_GETBATTERYLEVEL_METHOD_SIGNATURE, &mGetBatteryLevelMethodId}, {GCM_GETBATTERYSTATUS_METHOD_NAME, GCM_GETBATTERYSTATUS_METHOD_SIGNATURE, &mGetBatteryStatusMethodId}, {GCM_SETVIBRATION_METHOD_NAME, GCM_SETVIBRATION_METHOD_SIGNATURE, &mSetVibrationMethodId}, {GCM_SETLIGHT_METHOD_NAME, GCM_SETLIGHT_METHOD_SIGNATURE, &mSetLightMethodId}, {GCM_SETNATIVEREADY_METHOD_NAME, VOID_METHOD_SIGNATURE, &mSetNativeReadyMethodId}, {GCM_SETREPORTMOTIONEVENTS_METHOD_NAME, VOID_METHOD_SIGNATURE, &mSetReportMotionEventsMethodId}}; const size_t methodTableCount = ARRAY_COUNTOF(methodTable); for (size_t i = 0; i < methodTableCount; ++i) { const MethodTableEntry &entry = methodTable[i]; jmethodID methodID = env->GetMethodID( mGameControllerClass, entry.methodName, entry.methodSignature); if (methodID == NULL) { ALOGE("Failed to find %s init method", entry.methodName); return PADDLEBOAT_ERROR_INIT_GCM_FAILURE; } else { *entry.methodID = methodID; } } return PADDLEBOAT_NO_ERROR; } GameControllerManager::GameControllerManager(JNIEnv *env, jobject jcontext, ConstructorTag) { mContext = jcontext; mMouseData.timestamp = 0; mMouseData.buttonsDown = 0; mMouseData.mouseScrollDeltaH = 0; mMouseData.mouseScrollDeltaV = 0; mMouseData.mouseX = 0.0f; mMouseData.mouseY = 0.0f; mInitialized = true; memset(mMappingTable, 0, sizeof(mMappingTable)); mRemapEntryCount = GetInternalControllerDataCount(); const Paddleboat_Controller_Mapping_Data *mappingData = GetInternalControllerData(); const size_t copySize = mRemapEntryCount * sizeof(Paddleboat_Controller_Mapping_Data); memcpy(mMappingTable, mappingData, copySize); // Our minimum supported API level, we will retrieve the actual runtime API // level later on calling getApiLevel mApiLevel = 16; } GameControllerManager::~GameControllerManager() { mInitialized = false; } int32_t GameControllerManager::processInputEvent(const AInputEvent *event) { int32_t handledEvent = IGNORED_EVENT; if (event != nullptr) { GameControllerManager *gcm = getInstance(); if (gcm) { std::lock_guard lock(gcm->mUpdateMutex); const int32_t eventSource = AInputEvent_getSource(event); const int32_t dpadSource = eventSource & AINPUT_SOURCE_DPAD; const int32_t gamepadSource = eventSource & AINPUT_SOURCE_GAMEPAD; const int32_t joystickSource = eventSource & AINPUT_SOURCE_JOYSTICK; const int32_t mouseSource = eventSource & AINPUT_SOURCE_MOUSE; if (dpadSource == AINPUT_SOURCE_DPAD || gamepadSource == AINPUT_SOURCE_GAMEPAD || joystickSource == AINPUT_SOURCE_JOYSTICK) { const int32_t eventDeviceId = AInputEvent_getDeviceId(event); for (size_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) { if (gcm->mGameControllers[i].getConnectionIndex() >= 0) { if (gcm->mGameControllers[i].getControllerStatus() == PADDLEBOAT_CONTROLLER_ACTIVE) { const GameControllerDeviceInfo &deviceInfo = gcm->mGameControllers[i].getDeviceInfo(); if (deviceInfo.getInfo().mDeviceId == eventDeviceId) { const int32_t eventType = AInputEvent_getType(event); if (eventType == AINPUT_EVENT_TYPE_KEY) { handledEvent = gcm->processControllerKeyEvent( event, gcm->mGameControllers[i]); } else if (eventType == AINPUT_EVENT_TYPE_MOTION) { handledEvent = gcm->mGameControllers[i] .processMotionEvent(event); } break; } } } } } else if (mouseSource == AINPUT_SOURCE_MOUSE) { handledEvent = gcm->processMouseEvent(event); } #if defined LOG_INPUT_EVENTS LogInputEvent(event); #endif } } return handledEvent; } int32_t GameControllerManager::processGameActivityKeyInputEvent( const void *event, const size_t eventSize) { int32_t handledEvent = IGNORED_EVENT; if (event != nullptr) { GameControllerManager *gcm = getInstance(); if (gcm) { std::lock_guard lock(gcm->mUpdateMutex); const Paddleboat_GameActivityKeyEvent *keyEvent = reinterpret_cast( event); const int32_t eventSource = keyEvent->source; const int32_t eventDeviceId = keyEvent->deviceId; const int32_t dpadSource = eventSource & AINPUT_SOURCE_DPAD; const int32_t gamepadSource = eventSource & AINPUT_SOURCE_GAMEPAD; const int32_t joystickSource = eventSource & AINPUT_SOURCE_JOYSTICK; if (dpadSource == AINPUT_SOURCE_DPAD || gamepadSource == AINPUT_SOURCE_GAMEPAD || joystickSource == AINPUT_SOURCE_JOYSTICK) { for (size_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) { if (gcm->mGameControllers[i].getConnectionIndex() >= 0) { if (gcm->mGameControllers[i].getControllerStatus() == PADDLEBOAT_CONTROLLER_ACTIVE) { const GameControllerDeviceInfo &deviceInfo = gcm->mGameControllers[i].getDeviceInfo(); if (deviceInfo.getInfo().mDeviceId == eventDeviceId) { handledEvent = gcm->processControllerGameActivityKeyEvent( keyEvent, eventSize, gcm->mGameControllers[i]); break; } } } } } } } return handledEvent; } int32_t GameControllerManager::processGameActivityMotionInputEvent( const void *event, const size_t eventSize) { int32_t handledEvent = IGNORED_EVENT; if (event != nullptr) { GameControllerManager *gcm = getInstance(); if (gcm) { std::lock_guard lock(gcm->mUpdateMutex); const Paddleboat_GameActivityMotionEvent *motionEvent = reinterpret_cast( event); const int32_t eventSource = motionEvent->source; const int32_t eventDeviceId = motionEvent->deviceId; const int32_t dpadSource = eventSource & AINPUT_SOURCE_DPAD; const int32_t gamepadSource = eventSource & AINPUT_SOURCE_GAMEPAD; const int32_t joystickSource = eventSource & AINPUT_SOURCE_JOYSTICK; const int32_t mouseSource = eventSource & AINPUT_SOURCE_MOUSE; if (dpadSource == AINPUT_SOURCE_DPAD || gamepadSource == AINPUT_SOURCE_GAMEPAD || joystickSource == AINPUT_SOURCE_JOYSTICK) { for (size_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) { if (gcm->mGameControllers[i].getConnectionIndex() >= 0) { if (gcm->mGameControllers[i].getControllerStatus() == PADDLEBOAT_CONTROLLER_ACTIVE) { const GameControllerDeviceInfo &deviceInfo = gcm->mGameControllers[i].getDeviceInfo(); if (deviceInfo.getInfo().mDeviceId == eventDeviceId) { handledEvent = gcm->mGameControllers[i] .processGameActivityMotionEvent( motionEvent, eventSize); break; } } } } } else if (mouseSource == AINPUT_SOURCE_MOUSE) { handledEvent = gcm->processGameActivityMouseEvent( event, eventSize, eventDeviceId); } } } return handledEvent; } int32_t GameControllerManager::processControllerKeyEvent( const AInputEvent *event, GameController &gameController) { int32_t handledEvent = HANDLED_EVENT; const int32_t eventKeycode = AKeyEvent_getKeyCode(event); mLastKeyEventKeyCode = eventKeycode; if (eventKeycode == AKEYCODE_BACK) { if (!mBackButtonConsumed) { handledEvent = IGNORED_EVENT; } } else { handledEvent = gameController.processKeyEvent(event); } return handledEvent; } int32_t GameControllerManager::processControllerGameActivityKeyEvent( const Paddleboat_GameActivityKeyEvent *event, const size_t eventSize, GameController &gameController) { int32_t handledEvent = HANDLED_EVENT; const int32_t eventKeyCode = event->keyCode; mLastKeyEventKeyCode = eventKeyCode; if (eventKeyCode == AKEYCODE_BACK) { if (!mBackButtonConsumed) { handledEvent = IGNORED_EVENT; } } else { handledEvent = gameController.processGameActivityKeyEvent(event, eventSize); } return handledEvent; } int32_t GameControllerManager::processMouseEvent(const AInputEvent *event) { int32_t handledEvent = IGNORED_EVENT; const int32_t eventDeviceId = AInputEvent_getDeviceId(event); const int32_t eventType = AInputEvent_getType(event); // Always update the virtual pointer data in the appropriate controller data // structures if (eventType == AINPUT_EVENT_TYPE_MOTION) { for (size_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) { if (mGameControllers[i].getConnectionIndex() >= 0) { if (mGameControllers[i].getControllerStatus() == PADDLEBOAT_CONTROLLER_ACTIVE) { const Paddleboat_Controller_Info &controllerInfo = mGameControllers[i].getControllerInfo(); if (controllerInfo.deviceId == eventDeviceId) { Paddleboat_Controller_Data &controllerData = mGameControllers[i].getControllerData(); controllerData.virtualPointer.pointerX = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_X, 0); controllerData.virtualPointer.pointerY = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_Y, 0); const float axisP = AMotionEvent_getAxisValue( event, AMOTION_EVENT_AXIS_PRESSURE, 0); const bool hasTouchpadButton = ((controllerInfo.controllerFlags & PADDLEBOAT_CONTROLLER_FLAG_TOUCHPAD) != 0); if (hasTouchpadButton) { if (axisP > 0.0f) { controllerData.buttonsDown |= PADDLEBOAT_BUTTON_TOUCHPAD; } else { controllerData.buttonsDown &= (~PADDLEBOAT_BUTTON_TOUCHPAD); } } mGameControllers[i].setControllerDataDirty(true); // If this controller is our 'active' virtual mouse, // update the mouse data if (mMouseStatus == PADDLEBOAT_MOUSE_CONTROLLER_EMULATED && mMouseControllerIndex == static_cast(i)) { mMouseData.mouseX = controllerData.virtualPointer.pointerX; mMouseData.mouseY = controllerData.virtualPointer.pointerY; mMouseData.buttonsDown = static_cast( AMotionEvent_getButtonState(event)); mMouseData.buttonsDown |= axisP > 0.0f ? 1 : 0; updateMouseDataTimestamp(); } break; } } } } } if (mMouseStatus == PADDLEBOAT_MOUSE_PHYSICAL) { for (size_t i = 0; i < MAX_MOUSE_DEVICES; ++i) { if (mMouseDeviceIds[i] == eventDeviceId) { if (eventType == AINPUT_EVENT_TYPE_MOTION) { mMouseData.mouseX = AMotionEvent_getAxisValue( event, AMOTION_EVENT_AXIS_X, 0); mMouseData.mouseY = AMotionEvent_getAxisValue( event, AMOTION_EVENT_AXIS_Y, 0); const int32_t buttonState = AMotionEvent_getButtonState(event); mMouseData.buttonsDown = static_cast(buttonState); const float axisHScroll = AMotionEvent_getAxisValue( event, AMOTION_EVENT_AXIS_HSCROLL, 0); const float axisVScroll = AMotionEvent_getAxisValue( event, AMOTION_EVENT_AXIS_VSCROLL, 0); // These are treated as cumulative deltas and reset when the // calling application requests the mouse data. mMouseData.mouseScrollDeltaH += static_cast(axisHScroll); mMouseData.mouseScrollDeltaV += static_cast(axisVScroll); updateMouseDataTimestamp(); } handledEvent = HANDLED_EVENT; break; } } } return handledEvent; } int32_t GameControllerManager::processGameActivityMouseEvent( const void *event, const size_t eventSize, const int32_t eventDeviceId) { int32_t handledEvent = IGNORED_EVENT; // Always update the virtual pointer data in the appropriate controller data // structures const Paddleboat_GameActivityMotionEvent *motionEvent = reinterpret_cast(event); if (motionEvent->pointerCount > 0) { const Paddleboat_GameActivityPointerInfo *pointerInfo = motionEvent->pointers; for (size_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) { if (mGameControllers[i].getConnectionIndex() >= 0) { if (mGameControllers[i].getControllerStatus() == PADDLEBOAT_CONTROLLER_ACTIVE) { const Paddleboat_Controller_Info &controllerInfo = mGameControllers[i].getControllerInfo(); if (controllerInfo.deviceId == eventDeviceId) { Paddleboat_Controller_Data &controllerData = mGameControllers[i].getControllerData(); controllerData.virtualPointer.pointerX = pointerInfo->axisValues[AMOTION_EVENT_AXIS_X]; controllerData.virtualPointer.pointerY = pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y]; const float axisP = pointerInfo ->axisValues[AMOTION_EVENT_AXIS_PRESSURE]; const bool hasTouchpadButton = ((controllerInfo.controllerFlags & PADDLEBOAT_CONTROLLER_FLAG_TOUCHPAD) != 0); if (hasTouchpadButton) { if (axisP > 0.0f) { controllerData.buttonsDown |= PADDLEBOAT_BUTTON_TOUCHPAD; } else { controllerData.buttonsDown &= (~PADDLEBOAT_BUTTON_TOUCHPAD); } } mGameControllers[i].setControllerDataDirty(true); // If this controller is our 'active' virtual mouse, // update the mouse data if (mMouseStatus == PADDLEBOAT_MOUSE_CONTROLLER_EMULATED && mMouseControllerIndex == static_cast(i)) { mMouseData.mouseX = controllerData.virtualPointer.pointerX; mMouseData.mouseY = controllerData.virtualPointer.pointerY; mMouseData.buttonsDown = motionEvent->buttonState; mMouseData.buttonsDown |= axisP > 0.0f ? 1 : 0; updateMouseDataTimestamp(); } break; } } } } } if (mMouseStatus == PADDLEBOAT_MOUSE_PHYSICAL) { for (size_t i = 0; i < MAX_MOUSE_DEVICES; ++i) { if (mMouseDeviceIds[i] == eventDeviceId) { if (motionEvent->pointerCount > 0) { const Paddleboat_GameActivityPointerInfo *pointerInfo = motionEvent->pointers; mMouseData.mouseX = pointerInfo->axisValues[AMOTION_EVENT_AXIS_X]; mMouseData.mouseY = pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y]; const int32_t buttonState = motionEvent->buttonState; mMouseData.buttonsDown = static_cast(buttonState); const float axisHScroll = pointerInfo->axisValues[AMOTION_EVENT_AXIS_HSCROLL]; const float axisVScroll = pointerInfo->axisValues[AMOTION_EVENT_AXIS_VSCROLL]; // These are treated as cumulative deltas and reset when the // calling application requests the mouse data. mMouseData.mouseScrollDeltaH += static_cast(axisHScroll); mMouseData.mouseScrollDeltaV += static_cast(axisVScroll); updateMouseDataTimestamp(); } handledEvent = HANDLED_EVENT; break; } } } return handledEvent; } uint64_t GameControllerManager::getActiveAxisMask() { uint64_t returnMask = 0; GameControllerManager *gcm = getInstance(); if (gcm) { returnMask = gcm->mActiveAxisMask; } return returnMask; } void GameControllerManager::update(JNIEnv *env) { GameControllerManager *gcm = getInstance(); if (!gcm) { return; } if (!gcm->mGCMClassInitialized && gcm->mGameControllerObject != NULL) { gcm->mApiLevel = env->CallIntMethod(gcm->mGameControllerObject, gcm->mGetApiLevelMethodId); // Tell the GCM class we are ready to receive information about // controllers gcm->mGCMClassInitialized = true; jmethodID setNativeReady = env->GetMethodID( gcm->mGameControllerClass, GCM_SETNATIVEREADY_METHOD_NAME, VOID_METHOD_SIGNATURE); if (setNativeReady != NULL) { env->CallVoidMethod(gcm->mGameControllerObject, setNativeReady); } } if (gcm->mMotionDataCallback != nullptr && gcm->mMotionEventReporting == false) { // If a motion data callback is registered, tell the managed side to // start reporting motion event data env->CallVoidMethod(gcm->mGameControllerObject, gcm->mSetReportMotionEventsMethodId); gcm->mMotionEventReporting = true; } std::lock_guard lock(gcm->mUpdateMutex); // Process pending connections/disconnections if (gcm->mStatusCallback != nullptr) { for (size_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) { if (gcm->mGameControllers[i].getConnectionIndex() >= 0) { if (gcm->mGameControllers[i].getControllerStatus() == PADDLEBOAT_CONTROLLER_JUST_CONNECTED) { // Look for a mapping table entry for this controller // device, if one does not exist, nullptr is passed and // GameController will fallback to default axis and button // mapping. const Paddleboat_Controller_Mapping_Data *mapData = gcm->getMapForController(gcm->mGameControllers[i]); #if defined LOG_INPUT_EVENTS if (mapData != nullptr) { ALOGI("Found controller map for vId/pId: %x %x", gcm->mGameControllers[i] .getDeviceInfo() .getInfo() ->mVendorId, gcm->mGameControllers[i] .getDeviceInfo() .getInfo() ->mProductId); } else { ALOGI( "No controller map found, using defaults for " "vId/pId: %x %x", gcm->mGameControllers[i] .getDeviceInfo() .getInfo() ->mVendorId, gcm->mGameControllers[i] .getDeviceInfo() .getInfo() ->mProductId); } #endif gcm->mGameControllers[i].setupController(mapData); // Update the active axis mask to include any new axis used // by the new controller gcm->mActiveAxisMask |= gcm->mGameControllers[i].getControllerAxisMask(); gcm->mGameControllers[i].setControllerStatus( PADDLEBOAT_CONTROLLER_ACTIVE); if (gcm->mStatusCallback != nullptr) { #if defined LOG_INPUT_EVENTS ALOGI( "statusCallback " "PADDLEBOAT_CONTROLLER_JUST_CONNECTED on %d", static_cast(i)); #endif gcm->mStatusCallback( i, PADDLEBOAT_CONTROLLER_JUST_CONNECTED, gcm->mStatusCallbackUserData); } gcm->rescanVirtualMouseControllers(); } else if (gcm->mGameControllers[i].getControllerStatus() == PADDLEBOAT_CONTROLLER_JUST_DISCONNECTED) { gcm->mGameControllers[i].setControllerStatus( PADDLEBOAT_CONTROLLER_INACTIVE); // free the controller for reuse gcm->mGameControllers[i].setConnectionIndex(-1); if (gcm->mStatusCallback != nullptr) { #if defined LOG_INPUT_EVENTS ALOGI( "statusCallback " "PADDLEBOAT_CONTROLLER_JUST_DISCONNECTED on %d", static_cast(i)); #endif gcm->mStatusCallback( i, PADDLEBOAT_CONTROLLER_JUST_DISCONNECTED, gcm->mStatusCallbackUserData); } gcm->rescanVirtualMouseControllers(); } } } } gcm->updateBattery(env); } Paddleboat_ErrorCode GameControllerManager::getControllerData( const int32_t controllerIndex, Paddleboat_Controller_Data *controllerData) { Paddleboat_ErrorCode errorCode = PADDLEBOAT_NO_ERROR; if (controllerData != nullptr) { if (controllerIndex >= 0 && controllerIndex < PADDLEBOAT_MAX_CONTROLLERS) { GameControllerManager *gcm = getInstance(); if (gcm) { if (gcm->mGameControllers[controllerIndex] .getConnectionIndex() == controllerIndex) { if (gcm->mGameControllers[controllerIndex] .getControllerDataDirty()) { gcm->mGameControllers[controllerIndex] .setControllerDataDirty(false); } memcpy(controllerData, &gcm->mGameControllers[controllerIndex] .getControllerData(), sizeof(Paddleboat_Controller_Data)); } else { errorCode = PADDLEBOAT_ERROR_NO_CONTROLLER; } } else { errorCode = PADDLEBOAT_ERROR_NOT_INITIALIZED; } } else { errorCode = PADDLEBOAT_ERROR_INVALID_CONTROLLER_INDEX; } } else { errorCode = PADDLEBOAT_ERROR_INVALID_PARAMETER; } return errorCode; } Paddleboat_ErrorCode GameControllerManager::getControllerInfo( const int32_t controllerIndex, Paddleboat_Controller_Info *controllerInfo) { Paddleboat_ErrorCode errorCode = PADDLEBOAT_NO_ERROR; if (controllerInfo != nullptr) { if (controllerIndex >= 0 && controllerIndex < PADDLEBOAT_MAX_CONTROLLERS) { GameControllerManager *gcm = getInstance(); if (gcm) { if (gcm->mGameControllers[controllerIndex] .getConnectionIndex() == controllerIndex) { memcpy(controllerInfo, &gcm->mGameControllers[controllerIndex] .getControllerInfo(), sizeof(Paddleboat_Controller_Info)); } else { errorCode = PADDLEBOAT_ERROR_NO_CONTROLLER; } } else { errorCode = PADDLEBOAT_ERROR_NOT_INITIALIZED; } } else { errorCode = PADDLEBOAT_ERROR_INVALID_CONTROLLER_INDEX; } } else { errorCode = PADDLEBOAT_ERROR_INVALID_PARAMETER; } return errorCode; } Paddleboat_ErrorCode GameControllerManager::getControllerName( const int32_t controllerIndex, const size_t bufferSize, char *controllerName) { Paddleboat_ErrorCode errorCode = PADDLEBOAT_NO_ERROR; if (controllerName != nullptr) { if (controllerIndex >= 0 && controllerIndex < PADDLEBOAT_MAX_CONTROLLERS) { GameControllerManager *gcm = getInstance(); if (gcm) { if (gcm->mGameControllers[controllerIndex] .getConnectionIndex() == controllerIndex) { const GameControllerDeviceInfo &deviceInfo = gcm->mGameControllers[controllerIndex].getDeviceInfo(); strncpy(controllerName, deviceInfo.getName(), bufferSize); // Manually zero-terminate if the string was too long to fit const size_t nameLength = strlen(deviceInfo.getName()); if (nameLength >= bufferSize) { controllerName[bufferSize - 1] = '\0'; } } else { errorCode = PADDLEBOAT_ERROR_NO_CONTROLLER; } } else { errorCode = PADDLEBOAT_ERROR_NOT_INITIALIZED; } } else { errorCode = PADDLEBOAT_ERROR_INVALID_CONTROLLER_INDEX; } } else { errorCode = PADDLEBOAT_ERROR_INVALID_PARAMETER; } return errorCode; } Paddleboat_ControllerStatus GameControllerManager::getControllerStatus( const int32_t controllerIndex) { Paddleboat_ControllerStatus controllerStatus = PADDLEBOAT_CONTROLLER_INACTIVE; if (controllerIndex >= 0 && controllerIndex < PADDLEBOAT_MAX_CONTROLLERS) { GameControllerManager *gcm = getInstance(); if (gcm) { controllerStatus = gcm->mGameControllers[controllerIndex].getControllerStatus(); } } return controllerStatus; } bool GameControllerManager::isLightTypeSupported( const Paddleboat_Controller_Info &controllerInfo, const Paddleboat_LightType lightType) { bool isSupported = false; if (mGameControllerObject != NULL && mSetLightMethodId != NULL) { if (lightType == PADDLEBOAT_LIGHT_RGB) { if ((controllerInfo.controllerFlags & PADDLEBOAT_CONTROLLER_FLAG_LIGHT_RGB) != 0) { isSupported = true; } } else if (lightType == PADDLEBOAT_LIGHT_PLAYER_NUMBER) { if ((controllerInfo.controllerFlags & PADDLEBOAT_CONTROLLER_FLAG_LIGHT_PLAYER) != 0) { isSupported = true; } } } return isSupported; } Paddleboat_ErrorCode GameControllerManager::setControllerLight( const int32_t controllerIndex, const Paddleboat_LightType lightType, const uint32_t lightData, JNIEnv *env) { Paddleboat_ErrorCode errorCode = PADDLEBOAT_NO_ERROR; if (controllerIndex >= 0 && controllerIndex < PADDLEBOAT_MAX_CONTROLLERS) { GameControllerManager *gcm = getInstance(); if (gcm) { if (gcm->mGameControllers[controllerIndex].getConnectionIndex() == controllerIndex) { const Paddleboat_Controller_Info &controllerInfo = gcm->mGameControllers[controllerIndex].getControllerInfo(); if (gcm->isLightTypeSupported(controllerInfo, lightType)) { const jint jLightType = static_cast(lightType); const jint jLightData = static_cast(lightData); env->CallVoidMethod( gcm->mGameControllerObject, gcm->mSetLightMethodId, controllerInfo.deviceId, jLightType, jLightData); } else { errorCode = PADDLEBOAT_ERROR_FEATURE_NOT_SUPPORTED; } } else { errorCode = PADDLEBOAT_ERROR_NO_CONTROLLER; } } else { errorCode = PADDLEBOAT_ERROR_NOT_INITIALIZED; } } else { errorCode = PADDLEBOAT_ERROR_INVALID_CONTROLLER_INDEX; } return errorCode; } Paddleboat_ErrorCode GameControllerManager::setControllerVibrationData( const int32_t controllerIndex, const Paddleboat_Vibration_Data *vibrationData, JNIEnv *env) { Paddleboat_ErrorCode errorCode = PADDLEBOAT_NO_ERROR; if (vibrationData != nullptr) { if (controllerIndex >= 0 && controllerIndex < PADDLEBOAT_MAX_CONTROLLERS) { GameControllerManager *gcm = getInstance(); if (gcm) { if (gcm->mGameControllers[controllerIndex] .getConnectionIndex() == controllerIndex) { const Paddleboat_Controller_Info &controllerInfo = gcm->mGameControllers[controllerIndex] .getControllerInfo(); if ((controllerInfo.controllerFlags & PADDLEBOAT_CONTROLLER_FLAG_VIBRATION) != 0) { if (gcm->mGameControllerObject != NULL && gcm->mSetVibrationMethodId != NULL) { const jint intensityLeft = static_cast(vibrationData->intensityLeft * VIBRATION_INTENSITY_SCALE); const jint intensityRight = static_cast( vibrationData->intensityRight * VIBRATION_INTENSITY_SCALE); const jint durationLeft = static_cast(vibrationData->durationLeft); const jint durationRight = static_cast(vibrationData->durationRight); env->CallVoidMethod(gcm->mGameControllerObject, gcm->mSetVibrationMethodId, controllerInfo.deviceId, intensityLeft, durationLeft, intensityRight, durationRight); } } else { errorCode = PADDLEBOAT_ERROR_FEATURE_NOT_SUPPORTED; } } else { errorCode = PADDLEBOAT_ERROR_NO_CONTROLLER; } } else { errorCode = PADDLEBOAT_ERROR_NOT_INITIALIZED; } } else { errorCode = PADDLEBOAT_ERROR_INVALID_CONTROLLER_INDEX; } } else { errorCode = PADDLEBOAT_ERROR_INVALID_PARAMETER; } return errorCode; } GameControllerDeviceInfo *GameControllerManager::onConnection() { GameControllerDeviceInfo *deviceInfo = nullptr; GameControllerManager *gcm = getInstance(); if (gcm) { std::lock_guard lock(gcm->mUpdateMutex); for (size_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) { if (gcm->mGameControllers[i].getConnectionIndex() < 0) { gcm->mGameControllers[i].setConnectionIndex(i); gcm->mGameControllers[i].resetControllerData(); deviceInfo = &gcm->mGameControllers[i].getDeviceInfo(); gcm->mGameControllers[i].setControllerStatus( PADDLEBOAT_CONTROLLER_JUST_CONNECTED); #if defined LOG_INPUT_EVENTS ALOGI( "Setting PADDLEBOAT_CONTROLLER_JUST_CONNECTED on index %d", static_cast(i)); #endif break; } } } return deviceInfo; } void GameControllerManager::onDisconnection(const int32_t deviceId) { GameControllerManager *gcm = getInstance(); if (gcm) { std::lock_guard lock(gcm->mUpdateMutex); for (size_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) { if (gcm->mGameControllers[i].getConnectionIndex() >= 0) { const GameControllerDeviceInfo &deviceInfo = gcm->mGameControllers[i].getDeviceInfo(); if (deviceInfo.getInfo().mDeviceId == deviceId) { gcm->mGameControllers[i].setControllerStatus( PADDLEBOAT_CONTROLLER_JUST_DISCONNECTED); #if defined LOG_INPUT_EVENTS ALOGI( "Setting PADDLEBOAT_CONTROLLER_JUST_DISCONNECTED on " "index %d", static_cast(i)); #endif } } } } } Paddleboat_ErrorCode GameControllerManager::getMouseData( Paddleboat_Mouse_Data *mouseData) { Paddleboat_ErrorCode errorCode = PADDLEBOAT_NO_ERROR; if (mouseData != nullptr) { GameControllerManager *gcm = getInstance(); if (gcm) { if (gcm->mMouseStatus != PADDLEBOAT_MOUSE_NONE) { memcpy(mouseData, &gcm->mMouseData, sizeof(Paddleboat_Mouse_Data)); // We reset the scroll wheel(s) values after each read gcm->mMouseData.mouseScrollDeltaH = 0; gcm->mMouseData.mouseScrollDeltaV = 0; } else { errorCode = PADDLEBOAT_ERROR_NO_MOUSE; } } else { errorCode = PADDLEBOAT_ERROR_NOT_INITIALIZED; } } else { errorCode = PADDLEBOAT_ERROR_INVALID_PARAMETER; } return errorCode; } Paddleboat_MouseStatus GameControllerManager::getMouseStatus() { GameControllerManager *gcm = getInstance(); if (gcm) { return gcm->mMouseStatus; } return PADDLEBOAT_MOUSE_NONE; } void GameControllerManager::onMotionData(const int32_t deviceId, const int32_t motionType, const uint64_t timestamp, const float dataX, const float dataY, const float dataZ) { GameControllerManager *gcm = getInstance(); if (gcm) { if (gcm->mMotionDataCallback != nullptr) { for (size_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) { if (gcm->mGameControllers[i].getConnectionIndex() >= 0) { const GameControllerDeviceInfo &deviceInfo = gcm->mGameControllers[i].getDeviceInfo(); if (deviceInfo.getInfo().mDeviceId == deviceId) { Paddleboat_Motion_Data motionData; motionData.motionType = static_cast(motionType); motionData.timestamp = timestamp; motionData.motionX = dataX; motionData.motionY = dataY; motionData.motionZ = dataZ; gcm->mMotionDataCallback( i, &motionData, gcm->mMotionDataCallbackUserData); return; } } } } } } void GameControllerManager::onMouseConnection(const int32_t deviceId) { GameControllerManager *gcm = getInstance(); if (gcm) { for (size_t i = 0; i < MAX_MOUSE_DEVICES; ++i) { if (gcm->mMouseDeviceIds[i] == INVALID_MOUSE_ID) { gcm->mMouseDeviceIds[i] = deviceId; break; } } if (gcm->mMouseStatus != PADDLEBOAT_MOUSE_PHYSICAL) { gcm->mMouseStatus = PADDLEBOAT_MOUSE_PHYSICAL; if (gcm->mMouseCallback != nullptr) { gcm->mMouseCallback(gcm->mMouseStatus, gcm->mMouseCallbackUserData); } } } } void GameControllerManager::onMouseDisconnection(const int32_t deviceId) { GameControllerManager *gcm = getInstance(); if (gcm) { int mouseDeviceCount = 0; for (size_t i = 0; i < MAX_MOUSE_DEVICES; ++i) { if (gcm->mMouseDeviceIds[i] == deviceId) { gcm->mMouseDeviceIds[i] = INVALID_MOUSE_ID; } else if (gcm->mMouseDeviceIds[i] != INVALID_MOUSE_ID) { ++mouseDeviceCount; } } // If no other physical mice are connected, see if we downgrade to // a controller virtual mouse or no mouse at all if (mouseDeviceCount == 0) { gcm->mMouseStatus = (gcm->mMouseControllerIndex == INVALID_MOUSE_ID) ? PADDLEBOAT_MOUSE_NONE : PADDLEBOAT_MOUSE_CONTROLLER_EMULATED; if (gcm->mMouseCallback != nullptr) { gcm->mMouseCallback(gcm->mMouseStatus, gcm->mMouseCallbackUserData); } } } } // Make sure the 'virtual' mouse is the first active controller index // which supports a virtual pointer. If no mouse is currently active, // upgrade to a virtual mouse and send a mouse status callback. // If only a virtual mouse was active, and it vanished, set no mouse and // send a mouse status callback. void GameControllerManager::rescanVirtualMouseControllers() { mMouseControllerIndex = INVALID_MOUSE_ID; for (size_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) { if (mGameControllers[i].getControllerStatus() == PADDLEBOAT_CONTROLLER_ACTIVE) { if ((mGameControllers[i].getControllerInfo().controllerFlags & PADDLEBOAT_CONTROLLER_FLAG_VIRTUAL_MOUSE) != 0) { mMouseControllerIndex = i; if (mMouseStatus == PADDLEBOAT_MOUSE_NONE) { mMouseStatus = PADDLEBOAT_MOUSE_CONTROLLER_EMULATED; if (mMouseCallback != nullptr) { mMouseCallback(mMouseStatus, mMouseCallbackUserData); } } break; } } } // If no virtual mouse exists, downgrade to no mouse and send a mouse status // callback. if (mMouseControllerIndex == INVALID_MOUSE_ID && mMouseStatus == PADDLEBOAT_MOUSE_CONTROLLER_EMULATED) { mMouseStatus = PADDLEBOAT_MOUSE_NONE; if (mMouseCallback != nullptr) { mMouseCallback(mMouseStatus, mMouseCallbackUserData); } } } void GameControllerManager::setMotionDataCallback( Paddleboat_MotionDataCallback motionDataCallback, void *userData) { GameControllerManager *gcm = getInstance(); if (gcm) { gcm->mMotionDataCallback = motionDataCallback; gcm->mMotionDataCallbackUserData = userData; } } void GameControllerManager::setMouseStatusCallback( Paddleboat_MouseStatusCallback statusCallback, void *userData) { GameControllerManager *gcm = getInstance(); if (gcm) { gcm->mMouseCallback = statusCallback; gcm->mMouseCallbackUserData = userData; } } jclass GameControllerManager::getGameControllerClass() { GameControllerManager *gcm = getInstance(); if (!gcm) { return NULL; } return gcm->mGameControllerClass; } jobject GameControllerManager::getGameControllerObject() { GameControllerManager *gcm = getInstance(); if (!gcm) { return NULL; } return gcm->mGameControllerObject; } bool GameControllerManager::getBackButtonConsumed() { GameControllerManager *gcm = getInstance(); if (gcm) { return gcm->mBackButtonConsumed; } return false; } void GameControllerManager::setBackButtonConsumed(bool consumed) { GameControllerManager *gcm = getInstance(); if (gcm) { gcm->mBackButtonConsumed = consumed; } } void GameControllerManager::setControllerStatusCallback( Paddleboat_ControllerStatusCallback statusCallback, void *userData) { GameControllerManager *gcm = getInstance(); if (gcm) { gcm->mStatusCallback = statusCallback; gcm->mStatusCallbackUserData = userData; } } // device debug helper function int32_t GameControllerManager::getLastKeycode() { GameControllerManager *gcm = getInstance(); if (gcm) { return gcm->mLastKeyEventKeyCode; } return 0; } void GameControllerManager::onStop(JNIEnv *env) { GameControllerManager *gcm = getInstance(); if (!gcm) { return; } if (gcm->mGameControllerObject != NULL) { jmethodID onPauseID = env->GetMethodID(gcm->mGameControllerClass, GCM_ONSTOP_METHOD_NAME, VOID_METHOD_SIGNATURE); if (onPauseID != NULL) { env->CallVoidMethod(gcm->mGameControllerObject, onPauseID); } } } void GameControllerManager::onStart(JNIEnv *env) { GameControllerManager *gcm = getInstance(); if (!gcm) { return; } if (gcm->mGameControllerObject != NULL) { jmethodID onResumeID = env->GetMethodID(gcm->mGameControllerClass, GCM_ONSTART_METHOD_NAME, VOID_METHOD_SIGNATURE); if (onResumeID != NULL) { env->CallVoidMethod(gcm->mGameControllerObject, onResumeID); } } } void GameControllerManager::updateBattery(JNIEnv *env) { if (mBatteryWait <= 0) { mBatteryWait = BATTERY_REFRESH_WAIT; for (size_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) { if (mGameControllers[i].getControllerStatus() == PADDLEBOAT_CONTROLLER_ACTIVE) { const Paddleboat_Controller_Info &controllerInfo = mGameControllers[i].getControllerInfo(); if ((controllerInfo.controllerFlags & PADDLEBOAT_CONTROLLER_FLAG_BATTERY) != 0) { Paddleboat_Controller_Data &controllerData = mGameControllers[i].getControllerData(); const jint deviceId = controllerInfo.deviceId; jfloat batteryLevel = env->CallFloatMethod( mGameControllerObject, mGetBatteryLevelMethodId, deviceId); jint batteryStatus = env->CallIntMethod(mGameControllerObject, mGetBatteryStatusMethodId, deviceId); controllerData.battery.batteryLevel = batteryLevel; // Java 'enum' starts at 1, not 0. controllerData.battery.batteryStatus = static_cast(batteryStatus - 1); } } } } else { --mBatteryWait; } } void GameControllerManager::updateMouseDataTimestamp() { const auto timestamp = std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()) .count(); mMouseData.timestamp = static_cast(timestamp); } void GameControllerManager::addControllerRemapData( const Paddleboat_Remap_Addition_Mode addMode, const int32_t remapTableEntryCount, const Paddleboat_Controller_Mapping_Data *mappingData) { GameControllerManager *gcm = getInstance(); if (gcm) { if (addMode == PADDLEBOAT_REMAP_ADD_MODE_REPLACE_ALL) { gcm->mRemapEntryCount = (remapTableEntryCount < MAX_REMAP_TABLE_SIZE) ? remapTableEntryCount : MAX_REMAP_TABLE_SIZE; const size_t copySize = gcm->mRemapEntryCount * sizeof(Paddleboat_Controller_Mapping_Data); memcpy(gcm->mMappingTable, mappingData, copySize); } else if (addMode == PADDLEBOAT_REMAP_ADD_MODE_DEFAULT) { for (int32_t i = 0; i < remapTableEntryCount; ++i) { MappingTableSearch mapSearch(&gcm->mMappingTable[0], gcm->mRemapEntryCount); mapSearch.initSearchParameters( mappingData[i].vendorId, mappingData[i].productId, mappingData[i].minimumEffectiveApiLevel, mappingData[i].maximumEffectiveApiLevel); GameControllerMappingUtils::findMatchingMapEntry(&mapSearch); bool success = GameControllerMappingUtils::insertMapEntry( &mappingData[i], &mapSearch); if (!success) { break; } gcm->mRemapEntryCount += 1; } } } } int32_t GameControllerManager::getControllerRemapTableData( const int32_t destRemapTableEntryCount, Paddleboat_Controller_Mapping_Data *mappingData) { GameControllerManager *gcm = getInstance(); if (!gcm) { return 0; } if (mappingData != nullptr) { size_t copySize = (gcm->mRemapEntryCount < destRemapTableEntryCount) ? gcm->mRemapEntryCount : destRemapTableEntryCount; copySize *= sizeof(Paddleboat_Controller_Mapping_Data); memcpy(mappingData, gcm->mMappingTable, copySize); } return gcm->mRemapEntryCount; } const Paddleboat_Controller_Mapping_Data * GameControllerManager::getMapForController( const GameController &gameController) { const Paddleboat_Controller_Mapping_Data *returnMap = nullptr; const GameControllerDeviceInfo &deviceInfo = gameController.getDeviceInfo(); MappingTableSearch mapSearch(&mMappingTable[0], mRemapEntryCount); mapSearch.initSearchParameters(deviceInfo.getInfo().mVendorId, deviceInfo.getInfo().mProductId, mApiLevel, mApiLevel); bool success = GameControllerMappingUtils::findMatchingMapEntry(&mapSearch); if (success) { returnMap = &mapSearch.mappingRoot[mapSearch.tableIndex]; } return returnMap; } } // namespace paddleboat