1444 lines
60 KiB
C++
1444 lines
60 KiB
C++
/*
|
|
* 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 <android/api-level.h>
|
|
|
|
#include <cstdlib>
|
|
#include <memory>
|
|
|
|
#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" {
|
|
|
|
JNIEXPORT void JNICALL 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<jint *>(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);
|
|
}
|
|
}
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_com_google_android_games_paddleboat_GameControllerManager_onControllerDisconnected(
|
|
JNIEnv *env, jobject gcmObject, jint deviceId) {
|
|
paddleboat::GameControllerManager::onDisconnection(deviceId);
|
|
}
|
|
|
|
JNIEXPORT void JNICALL 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);
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_com_google_android_games_paddleboat_GameControllerManager_onMouseConnected(
|
|
JNIEnv *env, jobject gcmObject, jint deviceId) {
|
|
paddleboat::GameControllerManager::onMouseConnection(deviceId);
|
|
}
|
|
|
|
JNIEXPORT void JNICALL 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 = "<init>";
|
|
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<void *>(
|
|
Java_com_google_android_games_paddleboat_GameControllerManager_onControllerConnected)},
|
|
{"onControllerDisconnected", "(I)V",
|
|
reinterpret_cast<void *>(
|
|
Java_com_google_android_games_paddleboat_GameControllerManager_onControllerDisconnected)},
|
|
{"onMotionData", "(IIJFFF)V",
|
|
reinterpret_cast<void *>(
|
|
Java_com_google_android_games_paddleboat_GameControllerManager_onMotionData)},
|
|
{"onMouseConnected", "(I)V",
|
|
reinterpret_cast<void *>(
|
|
Java_com_google_android_games_paddleboat_GameControllerManager_onMouseConnected)},
|
|
{"onMouseDisconnected", "(I)V",
|
|
reinterpret_cast<void *>(
|
|
Java_com_google_android_games_paddleboat_GameControllerManager_onMouseDisconnected)},
|
|
};
|
|
|
|
std::mutex GameControllerManager::sInstanceMutex;
|
|
std::unique_ptr<GameControllerManager> GameControllerManager::sInstance;
|
|
|
|
Paddleboat_ErrorCode GameControllerManager::init(JNIEnv *env,
|
|
jobject jcontext) {
|
|
std::lock_guard<std::mutex> lock(sInstanceMutex);
|
|
if (sInstance) {
|
|
ALOGE("Attempted to initialize Paddleboat twice");
|
|
return PADDLEBOAT_ERROR_ALREADY_INITIALIZED;
|
|
}
|
|
|
|
sInstance = std::make_unique<GameControllerManager>(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<jclass>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(gcm->mUpdateMutex);
|
|
const Paddleboat_GameActivityKeyEvent *keyEvent =
|
|
reinterpret_cast<const Paddleboat_GameActivityKeyEvent *>(
|
|
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<std::mutex> lock(gcm->mUpdateMutex);
|
|
const Paddleboat_GameActivityMotionEvent *motionEvent =
|
|
reinterpret_cast<const Paddleboat_GameActivityMotionEvent *>(
|
|
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<int32_t>(i)) {
|
|
mMouseData.mouseX =
|
|
controllerData.virtualPointer.pointerX;
|
|
mMouseData.mouseY =
|
|
controllerData.virtualPointer.pointerY;
|
|
mMouseData.buttonsDown = static_cast<uint32_t>(
|
|
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<uint32_t>(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<int32_t>(axisHScroll);
|
|
mMouseData.mouseScrollDeltaV +=
|
|
static_cast<int32_t>(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<const Paddleboat_GameActivityMotionEvent *>(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<int32_t>(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<uint32_t>(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<int32_t>(axisHScroll);
|
|
mMouseData.mouseScrollDeltaV +=
|
|
static_cast<int32_t>(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<std::mutex> 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<int>(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<int>(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<jint>(lightType);
|
|
const jint jLightData = static_cast<jint>(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<jint>(vibrationData->intensityLeft *
|
|
VIBRATION_INTENSITY_SCALE);
|
|
const jint intensityRight = static_cast<jint>(
|
|
vibrationData->intensityRight *
|
|
VIBRATION_INTENSITY_SCALE);
|
|
const jint durationLeft =
|
|
static_cast<jint>(vibrationData->durationLeft);
|
|
const jint durationRight =
|
|
static_cast<jint>(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<std::mutex> 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<int>(i));
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return deviceInfo;
|
|
}
|
|
|
|
void GameControllerManager::onDisconnection(const int32_t deviceId) {
|
|
GameControllerManager *gcm = getInstance();
|
|
if (gcm) {
|
|
std::lock_guard<std::mutex> 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<int>(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<Paddleboat_Motion_Type>(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<Paddleboat_BatteryStatus>(batteryStatus -
|
|
1);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
--mBatteryWait;
|
|
}
|
|
}
|
|
|
|
void GameControllerManager::updateMouseDataTimestamp() {
|
|
const auto timestamp =
|
|
std::chrono::duration_cast<std::chrono::microseconds>(
|
|
std::chrono::steady_clock::now().time_since_epoch())
|
|
.count();
|
|
mMouseData.timestamp = static_cast<uint64_t>(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
|