diff options
Diffstat (limited to 'source/3rd-party/SDL2/src/hidapi/android')
4 files changed, 1191 insertions, 0 deletions
diff --git a/source/3rd-party/SDL2/src/hidapi/android/hid.cpp b/source/3rd-party/SDL2/src/hidapi/android/hid.cpp new file mode 100644 index 0000000..7b8d41c --- /dev/null +++ b/source/3rd-party/SDL2/src/hidapi/android/hid.cpp @@ -0,0 +1,1159 @@ +//=================== Copyright Valve Corporation, All rights reserved. ======= +// +// Purpose: A wrapper implementing "HID" API for Android +// +// This layer glues the hidapi API to Android's USB and BLE stack. +// +//============================================================================= + +#include <jni.h> +#include <android/log.h> +#include <pthread.h> +#include <errno.h> // For ETIMEDOUT and ECONNRESET +#include <stdlib.h> // For malloc() and free() +#include <string.h> // For memcpy() + +#define TAG "hidapi" + +// Have error log always available +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) + +#ifdef DEBUG +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) +#else +#define LOGV(...) +#define LOGD(...) +#endif + +#define SDL_JAVA_PREFIX org_libsdl_app +#define CONCAT1(prefix, class, function) CONCAT2(prefix, class, function) +#define CONCAT2(prefix, class, function) Java_ ## prefix ## _ ## class ## _ ## function +#define HID_DEVICE_MANAGER_JAVA_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, HIDDeviceManager, function) + +#include "../hidapi/hidapi.h" + +typedef uint32_t uint32; +typedef uint64_t uint64; + + +struct hid_device_ +{ + int m_nId; + int m_nDeviceRefCount; +}; + +static JavaVM *g_JVM; +static pthread_key_t g_ThreadKey; + +template<class T> +class hid_device_ref +{ +public: + hid_device_ref( T *pObject = nullptr ) : m_pObject( nullptr ) + { + SetObject( pObject ); + } + + hid_device_ref( const hid_device_ref &rhs ) : m_pObject( nullptr ) + { + SetObject( rhs.GetObject() ); + } + + ~hid_device_ref() + { + SetObject( nullptr ); + } + + void SetObject( T *pObject ) + { + if ( m_pObject && m_pObject->DecrementRefCount() == 0 ) + { + delete m_pObject; + } + + m_pObject = pObject; + + if ( m_pObject ) + { + m_pObject->IncrementRefCount(); + } + } + + hid_device_ref &operator =( T *pObject ) + { + SetObject( pObject ); + return *this; + } + + hid_device_ref &operator =( const hid_device_ref &rhs ) + { + SetObject( rhs.GetObject() ); + return *this; + } + + T *GetObject() const + { + return m_pObject; + } + + T* operator->() const + { + return m_pObject; + } + + operator bool() const + { + return ( m_pObject != nullptr ); + } + +private: + T *m_pObject; +}; + +class hid_mutex_guard +{ +public: + hid_mutex_guard( pthread_mutex_t *pMutex ) : m_pMutex( pMutex ) + { + pthread_mutex_lock( m_pMutex ); + } + ~hid_mutex_guard() + { + pthread_mutex_unlock( m_pMutex ); + } + +private: + pthread_mutex_t *m_pMutex; +}; + +class hid_buffer +{ +public: + hid_buffer() : m_pData( nullptr ), m_nSize( 0 ), m_nAllocated( 0 ) + { + } + + hid_buffer( const uint8_t *pData, size_t nSize ) : m_pData( nullptr ), m_nSize( 0 ), m_nAllocated( 0 ) + { + assign( pData, nSize ); + } + + ~hid_buffer() + { + delete[] m_pData; + } + + void assign( const uint8_t *pData, size_t nSize ) + { + if ( nSize > m_nAllocated ) + { + delete[] m_pData; + m_pData = new uint8_t[ nSize ]; + m_nAllocated = nSize; + } + + m_nSize = nSize; + memcpy( m_pData, pData, nSize ); + } + + void clear() + { + m_nSize = 0; + } + + size_t size() const + { + return m_nSize; + } + + const uint8_t *data() const + { + return m_pData; + } + +private: + uint8_t *m_pData; + size_t m_nSize; + size_t m_nAllocated; +}; + +class hid_buffer_pool +{ +public: + hid_buffer_pool() : m_nSize( 0 ), m_pHead( nullptr ), m_pTail( nullptr ), m_pFree( nullptr ) + { + } + + ~hid_buffer_pool() + { + clear(); + + while ( m_pFree ) + { + hid_buffer_entry *pEntry = m_pFree; + m_pFree = m_pFree->m_pNext; + delete pEntry; + } + } + + size_t size() const { return m_nSize; } + + const hid_buffer &front() const { return m_pHead->m_buffer; } + + void pop_front() + { + hid_buffer_entry *pEntry = m_pHead; + if ( pEntry ) + { + m_pHead = pEntry->m_pNext; + if ( !m_pHead ) + { + m_pTail = nullptr; + } + pEntry->m_pNext = m_pFree; + m_pFree = pEntry; + --m_nSize; + } + } + + void emplace_back( const uint8_t *pData, size_t nSize ) + { + hid_buffer_entry *pEntry; + + if ( m_pFree ) + { + pEntry = m_pFree; + m_pFree = m_pFree->m_pNext; + } + else + { + pEntry = new hid_buffer_entry; + } + pEntry->m_pNext = nullptr; + + if ( m_pTail ) + { + m_pTail->m_pNext = pEntry; + } + else + { + m_pHead = pEntry; + } + m_pTail = pEntry; + + pEntry->m_buffer.assign( pData, nSize ); + ++m_nSize; + } + + void clear() + { + while ( size() > 0 ) + { + pop_front(); + } + } + +private: + struct hid_buffer_entry + { + hid_buffer m_buffer; + hid_buffer_entry *m_pNext; + }; + + size_t m_nSize; + hid_buffer_entry *m_pHead; + hid_buffer_entry *m_pTail; + hid_buffer_entry *m_pFree; +}; + +static jbyteArray NewByteArray( JNIEnv* env, const uint8_t *pData, size_t nDataLen ) +{ + jbyteArray array = env->NewByteArray( nDataLen ); + jbyte *pBuf = env->GetByteArrayElements( array, NULL ); + memcpy( pBuf, pData, nDataLen ); + env->ReleaseByteArrayElements( array, pBuf, 0 ); + + return array; +} + +static char *CreateStringFromJString( JNIEnv *env, const jstring &sString ) +{ + size_t nLength = env->GetStringUTFLength( sString ); + const char *pjChars = env->GetStringUTFChars( sString, NULL ); + char *psString = (char*)malloc( nLength + 1 ); + memcpy( psString, pjChars, nLength ); + psString[ nLength ] = '\0'; + env->ReleaseStringUTFChars( sString, pjChars ); + return psString; +} + +static wchar_t *CreateWStringFromJString( JNIEnv *env, const jstring &sString ) +{ + size_t nLength = env->GetStringLength( sString ); + const jchar *pjChars = env->GetStringChars( sString, NULL ); + wchar_t *pwString = (wchar_t*)malloc( ( nLength + 1 ) * sizeof( wchar_t ) ); + wchar_t *pwChars = pwString; + for ( size_t iIndex = 0; iIndex < nLength; ++iIndex ) + { + pwChars[ iIndex ] = pjChars[ iIndex ]; + } + pwString[ nLength ] = '\0'; + env->ReleaseStringChars( sString, pjChars ); + return pwString; +} + +static wchar_t *CreateWStringFromWString( const wchar_t *pwSrc ) +{ + size_t nLength = wcslen( pwSrc ); + wchar_t *pwString = (wchar_t*)malloc( ( nLength + 1 ) * sizeof( wchar_t ) ); + memcpy( pwString, pwSrc, nLength * sizeof( wchar_t ) ); + pwString[ nLength ] = '\0'; + return pwString; +} + +static hid_device_info *CopyHIDDeviceInfo( const hid_device_info *pInfo ) +{ + hid_device_info *pCopy = new hid_device_info; + *pCopy = *pInfo; + pCopy->path = strdup( pInfo->path ); + pCopy->product_string = CreateWStringFromWString( pInfo->product_string ); + pCopy->manufacturer_string = CreateWStringFromWString( pInfo->manufacturer_string ); + pCopy->serial_number = CreateWStringFromWString( pInfo->serial_number ); + return pCopy; +} + +static void FreeHIDDeviceInfo( hid_device_info *pInfo ) +{ + free( pInfo->path ); + free( pInfo->serial_number ); + free( pInfo->manufacturer_string ); + free( pInfo->product_string ); + delete pInfo; +} + +static jclass g_HIDDeviceManagerCallbackClass; +static jobject g_HIDDeviceManagerCallbackHandler; +static jmethodID g_midHIDDeviceManagerOpen; +static jmethodID g_midHIDDeviceManagerSendOutputReport; +static jmethodID g_midHIDDeviceManagerSendFeatureReport; +static jmethodID g_midHIDDeviceManagerGetFeatureReport; +static jmethodID g_midHIDDeviceManagerClose; + +static uint64_t get_timespec_ms( const struct timespec &ts ) +{ + return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +class CHIDDevice +{ +public: + CHIDDevice( int nDeviceID, hid_device_info *pInfo ) + { + m_nId = nDeviceID; + m_pInfo = pInfo; + + // The Bluetooth Steam Controller needs special handling + const int VALVE_USB_VID = 0x28DE; + const int D0G_BLE2_PID = 0x1106; + if ( pInfo->vendor_id == VALVE_USB_VID && pInfo->product_id == D0G_BLE2_PID ) + { + m_bIsBLESteamController = true; + } + } + + ~CHIDDevice() + { + FreeHIDDeviceInfo( m_pInfo ); + + // Note that we don't delete m_pDevice, as the app may still have a reference to it + } + + int IncrementRefCount() + { + int nValue; + pthread_mutex_lock( &m_refCountLock ); + nValue = ++m_nRefCount; + pthread_mutex_unlock( &m_refCountLock ); + return nValue; + } + + int DecrementRefCount() + { + int nValue; + pthread_mutex_lock( &m_refCountLock ); + nValue = --m_nRefCount; + pthread_mutex_unlock( &m_refCountLock ); + return nValue; + } + + int GetId() + { + return m_nId; + } + + const hid_device_info *GetDeviceInfo() + { + return m_pInfo; + } + + hid_device *GetDevice() + { + return m_pDevice; + } + + void ExceptionCheck( JNIEnv *env, const char *pszMethodName ) + { + if ( env->ExceptionCheck() ) + { + // Get our exception + jthrowable jExcept = env->ExceptionOccurred(); + + // Clear the exception so we can call JNI again + env->ExceptionClear(); + + // Get our exception message + jclass jExceptClass = env->GetObjectClass( jExcept ); + jmethodID jMessageMethod = env->GetMethodID( jExceptClass, "getMessage", "()Ljava/lang/String;" ); + jstring jMessage = (jstring)( env->CallObjectMethod( jExcept, jMessageMethod ) ); + const char *pszMessage = env->GetStringUTFChars( jMessage, NULL ); + + // ...and log it. + LOGE( "CHIDDevice::%s threw an exception: %s", pszMethodName, pszMessage ); + + // Cleanup + env->ReleaseStringUTFChars( jMessage, pszMessage ); + env->DeleteLocalRef( jMessage ); + env->DeleteLocalRef( jExceptClass ); + env->DeleteLocalRef( jExcept ); + } + } + + bool BOpen() + { + // Make sure thread is attached to JVM/env + JNIEnv *env; + g_JVM->AttachCurrentThread( &env, NULL ); + pthread_setspecific( g_ThreadKey, (void*)env ); + + m_bIsWaitingForOpen = false; + m_bOpenResult = env->CallBooleanMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerOpen, m_nId ); + ExceptionCheck( env, "BOpen" ); + + if ( m_bIsWaitingForOpen ) + { + hid_mutex_guard cvl( &m_cvLock ); + + const int OPEN_TIMEOUT_SECONDS = 60; + struct timespec ts, endtime; + clock_gettime( CLOCK_REALTIME, &ts ); + endtime = ts; + endtime.tv_sec += OPEN_TIMEOUT_SECONDS; + do + { + if ( pthread_cond_timedwait( &m_cv, &m_cvLock, &endtime ) != 0 ) + { + break; + } + } + while ( m_bIsWaitingForOpen && get_timespec_ms( ts ) < get_timespec_ms( endtime ) ); + } + + if ( !m_bOpenResult ) + { + if ( m_bIsWaitingForOpen ) + { + LOGV( "Device open failed - timed out waiting for device permission" ); + } + else + { + LOGV( "Device open failed" ); + } + return false; + } + + m_pDevice = new hid_device; + m_pDevice->m_nId = m_nId; + m_pDevice->m_nDeviceRefCount = 1; + LOGD("Creating device %d (%p), refCount = 1\n", m_pDevice->m_nId, m_pDevice); + return true; + } + + void SetOpenPending() + { + m_bIsWaitingForOpen = true; + } + + void SetOpenResult( bool bResult ) + { + if ( m_bIsWaitingForOpen ) + { + m_bOpenResult = bResult; + m_bIsWaitingForOpen = false; + pthread_cond_signal( &m_cv ); + } + } + + void ProcessInput( const uint8_t *pBuf, size_t nBufSize ) + { + hid_mutex_guard l( &m_dataLock ); + + size_t MAX_REPORT_QUEUE_SIZE = 16; + if ( m_vecData.size() >= MAX_REPORT_QUEUE_SIZE ) + { + m_vecData.pop_front(); + } + m_vecData.emplace_back( pBuf, nBufSize ); + } + + int GetInput( unsigned char *data, size_t length ) + { + hid_mutex_guard l( &m_dataLock ); + + if ( m_vecData.size() == 0 ) + { +// LOGV( "hid_read_timeout no data available" ); + return 0; + } + + const hid_buffer &buffer = m_vecData.front(); + size_t nDataLen = buffer.size() > length ? length : buffer.size(); + if ( m_bIsBLESteamController ) + { + data[0] = 0x03; + memcpy( data + 1, buffer.data(), nDataLen ); + ++nDataLen; + } + else + { + memcpy( data, buffer.data(), nDataLen ); + } + m_vecData.pop_front(); + +// LOGV("Read %u bytes", nDataLen); +// LOGV("%02x %02x %02x %02x %02x %02x %02x %02x ....", +// data[0], data[1], data[2], data[3], +// data[4], data[5], data[6], data[7]); + + return nDataLen; + } + + int SendOutputReport( const unsigned char *pData, size_t nDataLen ) + { + // Make sure thread is attached to JVM/env + JNIEnv *env; + g_JVM->AttachCurrentThread( &env, NULL ); + pthread_setspecific( g_ThreadKey, (void*)env ); + + jbyteArray pBuf = NewByteArray( env, pData, nDataLen ); + int nRet = env->CallIntMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerSendOutputReport, m_nId, pBuf ); + ExceptionCheck( env, "SendOutputReport" ); + + env->DeleteLocalRef( pBuf ); + return nRet; + } + + int SendFeatureReport( const unsigned char *pData, size_t nDataLen ) + { + // Make sure thread is attached to JVM/env + JNIEnv *env; + g_JVM->AttachCurrentThread( &env, NULL ); + pthread_setspecific( g_ThreadKey, (void*)env ); + + jbyteArray pBuf = NewByteArray( env, pData, nDataLen ); + int nRet = env->CallIntMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerSendFeatureReport, m_nId, pBuf ); + ExceptionCheck( env, "SendFeatureReport" ); + env->DeleteLocalRef( pBuf ); + return nRet; + } + + void ProcessFeatureReport( const uint8_t *pBuf, size_t nBufSize ) + { + hid_mutex_guard cvl( &m_cvLock ); + if ( m_bIsWaitingForFeatureReport ) + { + m_featureReport.assign( pBuf, nBufSize ); + + m_bIsWaitingForFeatureReport = false; + m_nFeatureReportError = 0; + pthread_cond_signal( &m_cv ); + } + } + + int GetFeatureReport( unsigned char *pData, size_t nDataLen ) + { + // Make sure thread is attached to JVM/env + JNIEnv *env; + g_JVM->AttachCurrentThread( &env, NULL ); + pthread_setspecific( g_ThreadKey, (void*)env ); + + { + hid_mutex_guard cvl( &m_cvLock ); + if ( m_bIsWaitingForFeatureReport ) + { + LOGV( "Get feature report already ongoing... bail" ); + return -1; // Read already ongoing, we currently do not serialize, TODO + } + m_bIsWaitingForFeatureReport = true; + } + + jbyteArray pBuf = NewByteArray( env, pData, nDataLen ); + int nRet = env->CallBooleanMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerGetFeatureReport, m_nId, pBuf ) ? 0 : -1; + ExceptionCheck( env, "GetFeatureReport" ); + env->DeleteLocalRef( pBuf ); + if ( nRet < 0 ) + { + LOGV( "GetFeatureReport failed" ); + m_bIsWaitingForFeatureReport = false; + return -1; + } + + { + hid_mutex_guard cvl( &m_cvLock ); + if ( m_bIsWaitingForFeatureReport ) + { + LOGV("=== Going to sleep" ); + // Wait in CV until we are no longer waiting for a feature report. + const int FEATURE_REPORT_TIMEOUT_SECONDS = 2; + struct timespec ts, endtime; + clock_gettime( CLOCK_REALTIME, &ts ); + endtime = ts; + endtime.tv_sec += FEATURE_REPORT_TIMEOUT_SECONDS; + do + { + if ( pthread_cond_timedwait( &m_cv, &m_cvLock, &endtime ) != 0 ) + { + break; + } + } + while ( m_bIsWaitingForFeatureReport && get_timespec_ms( ts ) < get_timespec_ms( endtime ) ); + + // We are back + if ( m_bIsWaitingForFeatureReport ) + { + m_nFeatureReportError = -ETIMEDOUT; + m_bIsWaitingForFeatureReport = false; + } + LOGV( "=== Got feature report err=%d", m_nFeatureReportError ); + if ( m_nFeatureReportError != 0 ) + { + return m_nFeatureReportError; + } + } + + size_t uBytesToCopy = m_featureReport.size() > nDataLen ? nDataLen : m_featureReport.size(); + memcpy( pData, m_featureReport.data(), uBytesToCopy ); + m_featureReport.clear(); + LOGV( "=== Got %u bytes", uBytesToCopy ); + + return uBytesToCopy; + } + } + + void Close( bool bDeleteDevice ) + { + // Make sure thread is attached to JVM/env + JNIEnv *env; + g_JVM->AttachCurrentThread( &env, NULL ); + pthread_setspecific( g_ThreadKey, (void*)env ); + + env->CallVoidMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerClose, m_nId ); + ExceptionCheck( env, "Close" ); + + hid_mutex_guard dataLock( &m_dataLock ); + m_vecData.clear(); + + // Clean and release pending feature report reads + hid_mutex_guard cvLock( &m_cvLock ); + m_featureReport.clear(); + m_bIsWaitingForFeatureReport = false; + m_nFeatureReportError = -ECONNRESET; + pthread_cond_broadcast( &m_cv ); + + if ( bDeleteDevice ) + { + delete m_pDevice; + m_pDevice = nullptr; + } + } + +private: + pthread_mutex_t m_refCountLock = PTHREAD_MUTEX_INITIALIZER; + int m_nRefCount = 0; + int m_nId = 0; + hid_device_info *m_pInfo = nullptr; + hid_device *m_pDevice = nullptr; + bool m_bIsBLESteamController = false; + + pthread_mutex_t m_dataLock = PTHREAD_MUTEX_INITIALIZER; // This lock has to be held to access m_vecData + hid_buffer_pool m_vecData; + + // For handling get_feature_report + pthread_mutex_t m_cvLock = PTHREAD_MUTEX_INITIALIZER; // This lock has to be held to access any variables below + pthread_cond_t m_cv = PTHREAD_COND_INITIALIZER; + bool m_bIsWaitingForOpen = false; + bool m_bOpenResult = false; + bool m_bIsWaitingForFeatureReport = false; + int m_nFeatureReportError = 0; + hid_buffer m_featureReport; + +public: + hid_device_ref<CHIDDevice> next; +}; + +class CHIDDevice; +static pthread_mutex_t g_DevicesMutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t g_DevicesRefCountMutex = PTHREAD_MUTEX_INITIALIZER; +static hid_device_ref<CHIDDevice> g_Devices; + +static hid_device_ref<CHIDDevice> FindDevice( int nDeviceId ) +{ + hid_device_ref<CHIDDevice> pDevice; + + hid_mutex_guard l( &g_DevicesMutex ); + for ( pDevice = g_Devices; pDevice; pDevice = pDevice->next ) + { + if ( pDevice->GetId() == nDeviceId ) + { + break; + } + } + return pDevice; +} + +static void ThreadDestroyed(void* value) +{ + /* The thread is being destroyed, detach it from the Java VM and set the g_ThreadKey value to NULL as required */ + JNIEnv *env = (JNIEnv*) value; + if (env != NULL) { + g_JVM->DetachCurrentThread(); + pthread_setspecific(g_ThreadKey, NULL); + } +} + + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallback)(JNIEnv *env, jobject thiz); + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz); + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface ); + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID); + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, int nDeviceID, bool bOpened); + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, int nDeviceID); + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value); + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceFeatureReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value); + + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallback)(JNIEnv *env, jobject thiz ) +{ + LOGV( "HIDDeviceRegisterCallback()"); + + env->GetJavaVM( &g_JVM ); + + /* + * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread + * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this + */ + if (pthread_key_create(&g_ThreadKey, ThreadDestroyed) != 0) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error initializing pthread key"); + } + + if ( g_HIDDeviceManagerCallbackHandler != NULL ) + { + env->DeleteGlobalRef( g_HIDDeviceManagerCallbackClass ); + g_HIDDeviceManagerCallbackClass = NULL; + env->DeleteGlobalRef( g_HIDDeviceManagerCallbackHandler ); + g_HIDDeviceManagerCallbackHandler = NULL; + } + + g_HIDDeviceManagerCallbackHandler = env->NewGlobalRef( thiz ); + jclass objClass = env->GetObjectClass( thiz ); + if ( objClass ) + { + g_HIDDeviceManagerCallbackClass = reinterpret_cast< jclass >( env->NewGlobalRef( objClass ) ); + g_midHIDDeviceManagerOpen = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "openDevice", "(I)Z" ); + if ( !g_midHIDDeviceManagerOpen ) + { + __android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing openDevice" ); + } + g_midHIDDeviceManagerSendOutputReport = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "sendOutputReport", "(I[B)I" ); + if ( !g_midHIDDeviceManagerSendOutputReport ) + { + __android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing sendOutputReport" ); + } + g_midHIDDeviceManagerSendFeatureReport = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "sendFeatureReport", "(I[B)I" ); + if ( !g_midHIDDeviceManagerSendFeatureReport ) + { + __android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing sendFeatureReport" ); + } + g_midHIDDeviceManagerGetFeatureReport = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "getFeatureReport", "(I[B)Z" ); + if ( !g_midHIDDeviceManagerGetFeatureReport ) + { + __android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing getFeatureReport" ); + } + g_midHIDDeviceManagerClose = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "closeDevice", "(I)V" ); + if ( !g_midHIDDeviceManagerClose ) + { + __android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing closeDevice" ); + } + env->DeleteLocalRef( objClass ); + } +} + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallback)(JNIEnv *env, jobject thiz) +{ + LOGV("HIDDeviceReleaseCallback"); + if ( env->IsSameObject( thiz, g_HIDDeviceManagerCallbackHandler ) ) + { + env->DeleteGlobalRef( g_HIDDeviceManagerCallbackClass ); + g_HIDDeviceManagerCallbackClass = NULL; + env->DeleteGlobalRef( g_HIDDeviceManagerCallbackHandler ); + g_HIDDeviceManagerCallbackHandler = NULL; + } +} + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceConnected)(JNIEnv *env, jobject thiz, int nDeviceID, jstring sIdentifier, int nVendorId, int nProductId, jstring sSerialNumber, int nReleaseNumber, jstring sManufacturer, jstring sProduct, int nInterface ) +{ + LOGV( "HIDDeviceConnected() id=%d VID/PID = %.4x/%.4x, interface %d\n", nDeviceID, nVendorId, nProductId, nInterface ); + + hid_device_info *pInfo = new hid_device_info; + memset( pInfo, 0, sizeof( *pInfo ) ); + pInfo->path = CreateStringFromJString( env, sIdentifier ); + pInfo->vendor_id = nVendorId; + pInfo->product_id = nProductId; + pInfo->serial_number = CreateWStringFromJString( env, sSerialNumber ); + pInfo->release_number = nReleaseNumber; + pInfo->manufacturer_string = CreateWStringFromJString( env, sManufacturer ); + pInfo->product_string = CreateWStringFromJString( env, sProduct ); + pInfo->interface_number = nInterface; + + hid_device_ref<CHIDDevice> pDevice( new CHIDDevice( nDeviceID, pInfo ) ); + + hid_mutex_guard l( &g_DevicesMutex ); + hid_device_ref<CHIDDevice> pLast, pCurr; + for ( pCurr = g_Devices; pCurr; pLast = pCurr, pCurr = pCurr->next ) + { + continue; + } + if ( pLast ) + { + pLast->next = pDevice; + } + else + { + g_Devices = pDevice; + } +} + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenPending)(JNIEnv *env, jobject thiz, int nDeviceID) +{ + LOGV( "HIDDeviceOpenPending() id=%d\n", nDeviceID ); + hid_device_ref<CHIDDevice> pDevice = FindDevice( nDeviceID ); + if ( pDevice ) + { + pDevice->SetOpenPending(); + } +} + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceOpenResult)(JNIEnv *env, jobject thiz, int nDeviceID, bool bOpened) +{ + LOGV( "HIDDeviceOpenResult() id=%d, result=%s\n", nDeviceID, bOpened ? "true" : "false" ); + hid_device_ref<CHIDDevice> pDevice = FindDevice( nDeviceID ); + if ( pDevice ) + { + pDevice->SetOpenResult( bOpened ); + } +} + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceDisconnected)(JNIEnv *env, jobject thiz, int nDeviceID) +{ + LOGV( "HIDDeviceDisconnected() id=%d\n", nDeviceID ); + hid_device_ref<CHIDDevice> pDevice; + { + hid_mutex_guard l( &g_DevicesMutex ); + hid_device_ref<CHIDDevice> pLast, pCurr; + for ( pCurr = g_Devices; pCurr; pLast = pCurr, pCurr = pCurr->next ) + { + if ( pCurr->GetId() == nDeviceID ) + { + pDevice = pCurr; + + if ( pLast ) + { + pLast->next = pCurr->next; + } + else + { + g_Devices = pCurr->next; + } + } + } + } + if ( pDevice ) + { + pDevice->Close( false ); + } +} + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceInputReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value) +{ + jbyte *pBuf = env->GetByteArrayElements(value, NULL); + jsize nBufSize = env->GetArrayLength(value); + +// LOGV( "HIDDeviceInput() id=%d len=%u\n", nDeviceID, nBufSize ); + hid_device_ref<CHIDDevice> pDevice = FindDevice( nDeviceID ); + if ( pDevice ) + { + pDevice->ProcessInput( reinterpret_cast< const uint8_t* >( pBuf ), nBufSize ); + } + + env->ReleaseByteArrayElements(value, pBuf, 0); +} + +extern "C" +JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceFeatureReport)(JNIEnv *env, jobject thiz, int nDeviceID, jbyteArray value) +{ + jbyte *pBuf = env->GetByteArrayElements(value, NULL); + jsize nBufSize = env->GetArrayLength(value); + + LOGV( "HIDDeviceFeatureReport() id=%d len=%u\n", nDeviceID, nBufSize ); + hid_device_ref<CHIDDevice> pDevice = FindDevice( nDeviceID ); + if ( pDevice ) + { + pDevice->ProcessFeatureReport( reinterpret_cast< const uint8_t* >( pBuf ), nBufSize ); + } + + env->ReleaseByteArrayElements(value, pBuf, 0); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +extern "C" +{ + +int hid_init(void) +{ + return 0; +} + +struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + struct hid_device_info *root = NULL; + hid_mutex_guard l( &g_DevicesMutex ); + for ( hid_device_ref<CHIDDevice> pDevice = g_Devices; pDevice; pDevice = pDevice->next ) + { + const hid_device_info *info = pDevice->GetDeviceInfo(); + if ( ( vendor_id == 0 && product_id == 0 ) || + ( vendor_id == info->vendor_id && product_id == info->product_id ) ) + { + hid_device_info *dev = CopyHIDDeviceInfo( info ); + dev->next = root; + root = dev; + } + } + return root; +} + +void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs) +{ + while ( devs ) + { + struct hid_device_info *next = devs->next; + FreeHIDDeviceInfo( devs ); + devs = next; + } +} + +HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) +{ + // TODO: Implement + return NULL; +} + +HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path, int bExclusive) +{ + LOGV( "hid_open_path( %s )", path ); + + hid_device_ref< CHIDDevice > pDevice; + { + hid_mutex_guard r( &g_DevicesRefCountMutex ); + hid_mutex_guard l( &g_DevicesMutex ); + for ( hid_device_ref<CHIDDevice> pCurr = g_Devices; pCurr; pCurr = pCurr->next ) + { + if ( strcmp( pCurr->GetDeviceInfo()->path, path ) == 0 ) + { + hid_device *pValue = pCurr->GetDevice(); + if ( pValue ) + { + ++pValue->m_nDeviceRefCount; + LOGD("Incrementing device %d (%p), refCount = %d\n", pValue->m_nId, pValue, pValue->m_nDeviceRefCount); + return pValue; + } + + // Hold a shared pointer to the controller for the duration + pDevice = pCurr; + break; + } + } + } + if ( pDevice && pDevice->BOpen() ) + { + return pDevice->GetDevice(); + } + return NULL; +} + +int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length) +{ + LOGV( "hid_write id=%d length=%u", device->m_nId, length ); + hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId ); + if ( pDevice ) + { + return pDevice->SendOutputReport( data, length ); + } + return -1; // Controller was disconnected +} + +// TODO: Implement timeout? +int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *device, unsigned char *data, size_t length, int milliseconds) +{ +// LOGV( "hid_read_timeout id=%d length=%u timeout=%d", device->m_nId, length, milliseconds ); + hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId ); + if ( pDevice ) + { + return pDevice->GetInput( data, length ); + } + LOGV( "controller was disconnected" ); + return -1; // Controller was disconnected +} + +// TODO: Implement blocking +int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length) +{ + LOGV( "hid_read id=%d length=%u", device->m_nId, length ); + return hid_read_timeout( device, data, length, 0 ); +} + +// TODO: Implement? +int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock) +{ + return -1; +} + +int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length) +{ + LOGV( "hid_send_feature_report id=%d length=%u", device->m_nId, length ); + hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId ); + if ( pDevice ) + { + return pDevice->SendFeatureReport( data, length ); + } + return -1; // Controller was disconnected +} + + +// Synchronous operation. Will block until completed. +int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length) +{ + LOGV( "hid_get_feature_report id=%d length=%u", device->m_nId, length ); + hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId ); + if ( pDevice ) + { + return pDevice->GetFeatureReport( data, length ); + } + return -1; // Controller was disconnected +} + + +void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device) +{ + LOGV( "hid_close id=%d", device->m_nId ); + hid_mutex_guard r( &g_DevicesRefCountMutex ); + LOGD("Decrementing device %d (%p), refCount = %d\n", device->m_nId, device, device->m_nDeviceRefCount - 1); + if ( --device->m_nDeviceRefCount == 0 ) + { + hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId ); + if ( pDevice ) + { + pDevice->Close( true ); + } + else + { + delete device; + } + LOGD("Deleted device %p\n", device); + } + +} + +int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen) +{ + hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId ); + if ( pDevice ) + { + wcsncpy( string, pDevice->GetDeviceInfo()->manufacturer_string, maxlen ); + return 0; + } + return -1; +} + +int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen) +{ + hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId ); + if ( pDevice ) + { + wcsncpy( string, pDevice->GetDeviceInfo()->product_string, maxlen ); + return 0; + } + return -1; +} + +int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen) +{ + hid_device_ref<CHIDDevice> pDevice = FindDevice( device->m_nId ); + if ( pDevice ) + { + wcsncpy( string, pDevice->GetDeviceInfo()->serial_number, maxlen ); + return 0; + } + return -1; +} + +int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen) +{ + return -1; +} + +HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device) +{ + return NULL; +} + +int hid_exit(void) +{ + return 0; +} + +} diff --git a/source/3rd-party/SDL2/src/hidapi/android/jni/Android.mk b/source/3rd-party/SDL2/src/hidapi/android/jni/Android.mk new file mode 100644 index 0000000..4462e88 --- /dev/null +++ b/source/3rd-party/SDL2/src/hidapi/android/jni/Android.mk @@ -0,0 +1,16 @@ +LOCAL_PATH:= $(call my-dir) + +HIDAPI_ROOT_REL:= ../.. +HIDAPI_ROOT_ABS:= $(LOCAL_PATH)/../.. + +include $(CLEAR_VARS) + +LOCAL_CPPFLAGS += -std=c++11 + +LOCAL_SRC_FILES := \ + $(HIDAPI_ROOT_REL)/android/hid.cpp + +LOCAL_MODULE := libhidapi +LOCAL_LDLIBS := -llog + +include $(BUILD_SHARED_LIBRARY) diff --git a/source/3rd-party/SDL2/src/hidapi/android/jni/Application.mk b/source/3rd-party/SDL2/src/hidapi/android/jni/Application.mk new file mode 100644 index 0000000..4fc6ba5 --- /dev/null +++ b/source/3rd-party/SDL2/src/hidapi/android/jni/Application.mk @@ -0,0 +1,2 @@ +APP_STL := gnustl_static +APP_ABI := armeabi-v7a diff --git a/source/3rd-party/SDL2/src/hidapi/android/project.properties b/source/3rd-party/SDL2/src/hidapi/android/project.properties new file mode 100644 index 0000000..6e18427 --- /dev/null +++ b/source/3rd-party/SDL2/src/hidapi/android/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-21 |