From 3f83d2a82567660e0ed8b0336efbe7112a43edf5 Mon Sep 17 00:00:00 2001 From: Nikita Krapivin Date: Fri, 29 Oct 2021 16:00:03 -0700 Subject: [PATCH] android support --- .gitignore | 1 + android/Android.mk | 68 ++ android/FAudioGMS_JNI.c | 388 ++++++++++ android/build.ndk.sh | 24 + .../AndroidSource/Java/FAudioGMSBridge.java | 140 ++++ .../Sdk/org.screwyoyo.faudiogms/build.gradle | 15 + .../src/main/AndroidManifest.xml | 3 + .../main/java/org/libsdl/app/HIDDevice.java | 22 + .../app/HIDDeviceBLESteamController.java | 649 +++++++++++++++++ .../java/org/libsdl/app/HIDDeviceManager.java | 685 ++++++++++++++++++ .../java/org/libsdl/app/HIDDeviceUSB.java | 309 ++++++++ .../src/main/java/org/libsdl/app/SDL.java | 83 +++ .../main/java/org/libsdl/app/SDLActivity.java | 616 ++++++++++++++++ .../java/org/libsdl/app/SDLAudioManager.java | 390 ++++++++++ .../org/libsdl/app/SDLControllerManager.java | 92 +++ .../screwyoyo/faudiogms/FAudioGMSNative.java | 81 +++ .../FAudioGMS/AndroidSource/libs/.gitkeep | 0 .../libs/arm64-v8a/libFAudioGMS.so | 3 + .../AndroidSource/libs/arm64-v8a/libSDL2.so | 3 + .../AndroidSource/libs/arm64-v8a/libhidapi.so | 3 + .../libs/armeabi-v7a/libFAudioGMS.so | 3 + .../AndroidSource/libs/armeabi-v7a/libSDL2.so | 3 + .../libs/armeabi-v7a/libhidapi.so | 3 + .../AndroidSource/libs/x86/libFAudioGMS.so | 3 + .../AndroidSource/libs/x86/libSDL2.so | 3 + .../AndroidSource/libs/x86/libhidapi.so | 3 + .../AndroidSource/libs/x86_64/libFAudioGMS.so | 3 + .../AndroidSource/libs/x86_64/libSDL2.so | 3 + .../AndroidSource/libs/x86_64/libhidapi.so | 3 + gamemaker/extensions/FAudioGMS/FAudioGMS.yy | 15 +- .../FAudioGMS/FAudioGMSAndroidDummy.ext | 0 .../FAudioGMS_Scripts/FAudioGMS_Scripts.gml | 11 +- 32 files changed, 3619 insertions(+), 9 deletions(-) create mode 100644 android/Android.mk create mode 100644 android/FAudioGMS_JNI.c create mode 100644 android/build.ndk.sh create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/Java/FAudioGMSBridge.java create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/build.gradle create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/AndroidManifest.xml create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDevice.java create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDeviceManager.java create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDeviceUSB.java create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDL.java create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDLActivity.java create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDLAudioManager.java create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDLControllerManager.java create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/screwyoyo/faudiogms/FAudioGMSNative.java create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/libs/.gitkeep create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libFAudioGMS.so create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libSDL2.so create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libhidapi.so create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libFAudioGMS.so create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libSDL2.so create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libhidapi.so create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libFAudioGMS.so create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libSDL2.so create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libhidapi.so create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libFAudioGMS.so create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libSDL2.so create mode 100644 gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libhidapi.so create mode 100644 gamemaker/extensions/FAudioGMS/FAudioGMSAndroidDummy.ext diff --git a/.gitignore b/.gitignore index 328a978..79be370 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .vs visualc/x64 build/ +android/buildandroid/ diff --git a/android/Android.mk b/android/Android.mk new file mode 100644 index 0000000..616df6b --- /dev/null +++ b/android/Android.mk @@ -0,0 +1,68 @@ +# FAudioGMS Android.mk file +# PS: Expect hell + +SAVED_LOCAL_PATH := $(call my-dir) +LOCAL_PATH := $(SAVED_LOCAL_PATH) +SDL_PATH := $(LOCAL_PATH)/../lib/SDL +FAUDIO_PATH := $(LOCAL_PATH)/../lib/FAudio +FAUDIOGMS_PATH := $(LOCAL_PATH)/.. + +# First we import SDL 2 + +include $(SDL_PATH)/Android.mk + +# Then we compile FAudio as a static library + +include $(CLEAR_VARS) + +LOCAL_PATH := $(SAVED_LOCAL_PATH) +LOCAL_MODULE := FAudio_static +LOCAL_MODULE_FILENAME := libFAudio + +LOCAL_SHARED_LIBRARIES := SDL2 + +LOCAL_C_INCLUDES := $(SDL_PATH)/include $(FAUDIO_PATH)/include $(FAUDIO_PATH)/src + +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) + +LOCAL_LDLIBS := + +LOCAL_EXPORT_LDLIBS := -ldl -llog -landroid + +LOCAL_SRC_FILES := \ + $(FAUDIO_PATH)/src/F3DAudio.c \ + $(FAUDIO_PATH)/src/FACT3D.c \ + $(FAUDIO_PATH)/src/FACT.c \ + $(FAUDIO_PATH)/src/FACT_internal.c \ + $(FAUDIO_PATH)/src/FAPOBase.c \ + $(FAUDIO_PATH)/src/FAPOFX.c \ + $(FAUDIO_PATH)/src/FAPOFX_echo.c \ + $(FAUDIO_PATH)/src/FAPOFX_eq.c \ + $(FAUDIO_PATH)/src/FAPOFX_masteringlimiter.c \ + $(FAUDIO_PATH)/src/FAPOFX_reverb.c \ + $(FAUDIO_PATH)/src/FAudio.c \ + $(FAUDIO_PATH)/src/FAudioFX_reverb.c \ + $(FAUDIO_PATH)/src/FAudioFX_volumemeter.c \ + $(FAUDIO_PATH)/src/FAudio_internal.c \ + $(FAUDIO_PATH)/src/FAudio_internal_simd.c \ + $(FAUDIO_PATH)/src/FAudio_operationset.c \ + $(FAUDIO_PATH)/src/FAudio_platform_sdl2.c \ + $(FAUDIO_PATH)/src/FAudio_platform_win32.c \ + $(FAUDIO_PATH)/src/XNA_Song.c \ + $(FAUDIO_PATH)/src/FAudio_gstreamer.c + +include $(BUILD_STATIC_LIBRARY) + +# And then we do our stuff... + +include $(CLEAR_VARS) + +LOCAL_PATH := $(SAVED_LOCAL_PATH) + +LOCAL_MODULE := FAudioGMS +# Tell ndk-build we rely on these two fellas: +LOCAL_SHARED_LIBRARIES := SDL2 FAudio_static +LOCAL_C_INCLUDES := $(SDL_PATH)/include $(FAUDIO_PATH)/include $(FAUDIOGMS_PATH)/src +LOCAL_SRC_FILES := $(FAUDIOGMS_PATH)/src/FAudioGMS.c $(LOCAL_PATH)/FAudioGMS_JNI.c + +include $(BUILD_SHARED_LIBRARY) diff --git a/android/FAudioGMS_JNI.c b/android/FAudioGMS_JNI.c new file mode 100644 index 0000000..d4f1d46 --- /dev/null +++ b/android/FAudioGMS_JNI.c @@ -0,0 +1,388 @@ +/* FAudioGMS - Game Maker FAudio bindings in C + * + * Copyright (c) 2021 Evan Hemsley + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + * + * Evan "cosmonaut" Hemsley + * + */ + +/* These are the Native -> JNI conv wrappers, they must only be built for Android */ +#ifdef __ANDROID__ + +#include +#include + +/* +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1Init + +JNIEXPORT: export this function for JNI +jdouble: return type, a GameMaker function must always return something +JNICALL: JNI calling convention + +Java_class_path_here_classNameHere_Function_Name_Here + +classpath: org.screwyoyo.faudiogms +classname: FAudioGMSNative +function name: FAudioGMSNative_FAudioGMS_1Init + +underscores must be escaped with _1 +*/ + +/* replace this with -1.0 or NAN if you wish... */ +/* ideally, a jdouble should map to a double */ +#define NOTHING ((jdouble)0.0) + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1Init +(JNIEnv* jniEnv, jclass jniThis, jdouble _spatialDistanceScale, jdouble _timestep) +{ + FAudioGMS_Init(_spatialDistanceScale, _timestep); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1StaticSound_1LoadWAV +(JNIEnv* jniEnv, jclass jniThis, jstring _filePath) +{ + jboolean isCopy; + const char* filePath; + jdouble ret; + + filePath = (*jniEnv)->GetStringUTFChars(jniEnv, _filePath, &isCopy); + ret = FAudioGMS_StaticSound_LoadWAV((char *)filePath); + (*jniEnv)->ReleaseStringUTFChars(jniEnv, _filePath, filePath); + return ret; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1StaticSound_1CreateSoundInstance +(JNIEnv* jniEnv, jclass jniThis, jdouble _staticSoundID) +{ + return (jdouble)FAudioGMS_StaticSound_CreateSoundInstance(_staticSoundID); +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1StaticSound_1Destroy +(JNIEnv* jniEnv, jclass jniThis, jdouble _staticSoundID) +{ + FAudioGMS_StaticSound_Destroy(_staticSoundID); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1StreamingSound_1LoadOGG +(JNIEnv* jniEnv, jclass jniThis, jstring _filepath) +{ + jboolean isCopy; + const char* filepath; + jdouble ret; + + filepath = (*jniEnv)->GetStringUTFChars(jniEnv, _filepath, &isCopy); + ret = FAudioGMS_StreamingSound_LoadOGG((char *)filepath); + (*jniEnv)->ReleaseStringUTFChars(jniEnv, _filepath, filepath); + return ret; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1Play +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID, jdouble _loop) +{ + FAudioGMS_SoundInstance_Play(_soundInstanceID, _loop); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1Pause +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID) +{ + FAudioGMS_SoundInstance_Pause(_soundInstanceID); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1Stop +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID) +{ + FAudioGMS_SoundInstance_Stop(_soundInstanceID); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1SetPan +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID, jdouble _pan) +{ + FAudioGMS_SoundInstance_SetPan(_soundInstanceID, _pan); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1SetPitch +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID, jdouble _pitch) +{ + FAudioGMS_SoundInstance_SetPitch(_soundInstanceID, _pitch); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1SetVolume +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID, jdouble _volume) +{ + FAudioGMS_SoundInstance_SetVolume(_soundInstanceID, _volume); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1Set3DPosition +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID, jdouble _x, jdouble _y, jdouble _z) +{ + FAudioGMS_SoundInstance_Set3DPosition(_soundInstanceID, _x, _y, _z); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1Set3DVelocity +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID, jdouble _xVelocity, jdouble _yVelocity, jdouble _zVelocity) +{ + FAudioGMS_SoundInstance_Set3DVelocity(_soundInstanceID, _xVelocity, _yVelocity, _zVelocity); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1SetTrackPositionInSeconds +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID, jdouble _trackPositionInSeconds) +{ + FAudioGMS_SoundInstance_SetTrackPositionInSeconds(_soundInstanceID, _trackPositionInSeconds); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1SetVolumeOverTime +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID, jdouble _volume, jdouble _milliseconds) +{ + FAudioGMS_SoundInstance_SetVolumeOverTime(_soundInstanceID, _volume, _milliseconds); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1SetLowPassFilter +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID, jdouble _lowPassFilter, jdouble _Q) +{ + FAudioGMS_SoundInstance_SetLowPassFilter(_soundInstanceID, _lowPassFilter, _Q); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1SetHighPassFilter +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID, jdouble _highPassFilter, jdouble _Q) +{ + FAudioGMS_SoundInstance_SetHighPassFilter(_soundInstanceID, _highPassFilter, _Q); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1SetBandPassFilter +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID, jdouble _bandPassFilter, jdouble _Q) +{ + FAudioGMS_SoundInstance_SetBandPassFilter(_soundInstanceID, _bandPassFilter, _Q); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1GetPitch +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID) +{ + return (jdouble)FAudioGMS_SoundInstance_GetPitch(_soundInstanceID); +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1GetVolume +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID) +{ + return (jdouble)FAudioGMS_SoundInstance_GetVolume(_soundInstanceID); +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1GetTrackLengthInSeconds +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID) +{ + return (jdouble)FAudioGMS_SoundInstance_GetTrackLengthInSeconds(_soundInstanceID); +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1GetTrackPositionInSeconds +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID) +{ + return (jdouble)FAudioGMS_SoundInstance_GetTrackPositionInSeconds(_soundInstanceID); +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1Destroy +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID) +{ + FAudioGMS_SoundInstance_Destroy(_soundInstanceID); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1DestroyWhenFinished +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID) +{ + FAudioGMS_SoundInstance_DestroyWhenFinished(_soundInstanceID); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1EffectChain_1Create +(JNIEnv* jniEnv, jclass jniThis) +{ + return (jdouble)FAudioGMS_EffectChain_Create(); +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1EffectChain_1AddDefaultReverb +(JNIEnv* jniEnv, jclass jniThis, jdouble _effectChainID) +{ + FAudioGMS_EffectChain_AddDefaultReverb(_effectChainID); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1EffectChain_1AddReverb +(JNIEnv* jniEnv, jclass jniThis, + jdouble _effectChainID, + jdouble _wetDryMix, + jdouble _reflectionsDelay, + jdouble _reverbDelay, + jdouble _earlyDiffusion, + jdouble _lateDiffusion, + jdouble _lowEQGain, + jdouble _lowEQCutoff, + jdouble _highEQGain, + jdouble _highEQCutoff, + jdouble _reflectionsGain, + jdouble _reverbGain, + jdouble _decayTime, + jdouble _density, + jdouble _roomSize) +{ + FAudioGMS_EffectChain_AddReverb( + _effectChainID, + _wetDryMix, + _reflectionsDelay, + _reverbDelay, + _earlyDiffusion, + _lateDiffusion, + _lowEQGain, + _lowEQCutoff, + _highEQGain, + _highEQCutoff, + _reflectionsGain, + _reverbGain, + _decayTime, + _density, + _roomSize + ); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1EffectChain_1Destroy +(JNIEnv* jniEnv, jclass jniThis, jdouble _effectChainID) +{ + FAudioGMS_EffectChain_Destroy(_effectChainID); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1SetEffectChain +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID, jdouble _effectChainID, jdouble _effectGain) +{ + FAudioGMS_SoundInstance_SetEffectChain(_soundInstanceID, _effectChainID, _effectGain); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SoundInstance_1SetEffectGain +(JNIEnv* jniEnv, jclass jniThis, jdouble _soundInstanceID, jdouble _effectGain) +{ + FAudioGMS_SoundInstance_SetEffectGain(_soundInstanceID, _effectGain); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SetListenerPosition +(JNIEnv* jniEnv, jclass jniThis, jdouble _x, jdouble _y, jdouble _z) +{ + FAudioGMS_SetListenerPosition(_x, _y, _z); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1SetListenerVelocity +(JNIEnv* jniEnv, jclass jniThis, jdouble _xVelocity, jdouble _yVelocity, jdouble _zVelocity) +{ + FAudioGMS_SetListenerVelocity(_xVelocity, _yVelocity, _zVelocity); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1PauseAll +(JNIEnv* jniEnv, jclass jniThis) +{ + FAudioGMS_PauseAll(); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1ResumeAll +(JNIEnv* jniEnv, jclass jniThis) +{ + FAudioGMS_ResumeAll(); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1StopAll +(JNIEnv* jniEnv, jclass jniThis) +{ + FAudioGMS_StopAll(); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1Update +(JNIEnv* jniEnv, jclass jniThis) +{ + FAudioGMS_Update(); + return NOTHING; +} + +JNIEXPORT jdouble JNICALL +Java_org_screwyoyo_faudiogms_FAudioGMSNative_FAudioGMS_1Destroy +(JNIEnv* jniEnv, jclass jniThis) +{ + FAudioGMS_Destroy(); + return NOTHING; +} + +#endif /* __ANDROID__ */ + +/* Do nothing for other platforms, because they, thankly, do not require JNI bindings... */ diff --git a/android/build.ndk.sh b/android/build.ndk.sh new file mode 100644 index 0000000..77b61f1 --- /dev/null +++ b/android/build.ndk.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +cd `dirname $0` +# rm -rf buildandroid +mkdir -p buildandroid + +# Make sure you have ndk-build and Android Sdk stuff in your $PATH! + +ndk-build \ + NDK_PROJECT_PATH=null \ + APP_BUILD_SCRIPT=Android.mk \ + APP_ABI="armeabi-v7a arm64-v8a x86 x86_64" \ + APP_PLATFORM=android-16 \ + APP_MODULES="SDL2 FAudio_static FAudioGMS" \ + NDK_OUT=buildandroid/obj \ + NDK_LIBS_OUT=buildandroid/lib + + +# Update gamemaker project folder.. + +cp -rf buildandroid/lib/* ../gamemaker/extensions/FAudioGMS/AndroidSource/libs +rm -rf buildandroid + +# we're done here. diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/Java/FAudioGMSBridge.java b/gamemaker/extensions/FAudioGMS/AndroidSource/Java/FAudioGMSBridge.java new file mode 100644 index 0000000..8fa5470 --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/Java/FAudioGMSBridge.java @@ -0,0 +1,140 @@ +package ${YYAndroidPackageName}; /* this class will reside in Runner's package namespace */ + +import java.lang.String; +import android.util.Log; + +import android.content.Intent; +import android.content.res.Configuration; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.app.Dialog; +import android.view.MotionEvent; + +import org.screwyoyo.faudiogms.FAudioGMSNative; +import org.libsdl.app.SDLActivity; +import org.libsdl.app.SDL; +import org.libsdl.app.SDLAudioManager; +import com.yoyogames.runner.RunnerJNILib; + +import android.content.res.AssetManager; + +public class FAudioGMSBridge extends FAudioGMSNative implements IExtensionBase +{ + public SDLActivity sdl; + public boolean paused; + + public FAudioGMSBridge() + { + super(); + paused = false; + SDL.setContext(RunnerJNILib.GetApplicationContext()); + sdl = new SDLActivity(); + } + + public void Init() + { + SDL.setContext(RunnerJNILib.GetApplicationContext()); + sdl.onCreate(null); + } + + public void onStart() + { + SDL.setContext(RunnerJNILib.GetApplicationContext()); + sdl.onStart(); + } + + public void onRestart() + { + onStart(); + } + + public void onStop() + { + sdl.onStop(); + } + + public void onDestroy() + { + sdl.onDestroy(); + } + + public void onPause() + { + sdl.onPause(); + if (!paused) + { + paused = true; + FAudioGMS_PauseAll(); + } + } + + public void onResume() + { + sdl.onResume(); + if (paused) + { + paused = false; + FAudioGMS_ResumeAll(); + } + } + + public void onWindowFocusChanged(boolean hasFocus) + { + sdl.onWindowFocusChanged(hasFocus); + } + + public void onConfigurationChanged(Configuration newConfig) + { + sdl.onConfigurationChanged(newConfig); + } + + public void onRequestPermissionsResult(int requestCode,String permissions[], int[] grantResults) + { + sdl.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + public Dialog onCreateDialog(int id) + { + return null; + } + + public boolean onTouchEvent(final MotionEvent event) + { + return false; + } + + public boolean onGenericMotionEvent(MotionEvent event) + { + return false; + } + + public boolean dispatchKeyEvent(KeyEvent event) + { + return false; + } + + public boolean dispatchGenericMotionEvent(MotionEvent event) + { + return false; + } + + public boolean performClick() + { + return false; + } + + public void onNewIntent(android.content.Intent newIntent) + { + + } + + public void onActivityResult(int requestCode, int resultCode, Intent data){} + public boolean onKeyLongPress(int keyCode, KeyEvent event){return false;} + public boolean onCreateOptionsMenu( Menu menu ){return false;} + public boolean onOptionsItemSelected( MenuItem item ){return false;} + + public boolean onKeyDown( int keyCode, KeyEvent event ) + { return false;} + public boolean onKeyUp( int keyCode, KeyEvent event ){return false;} +} diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/build.gradle b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/build.gradle new file mode 100644 index 0000000..7c5457f --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/build.gradle @@ -0,0 +1,15 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion 28 +} + +repositories { + mavenCentral() +} + +dependencies { + compile 'com.getkeepsafe.relinker:relinker:1.4.4' +} diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/AndroidManifest.xml b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5ae9c9e --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDevice.java b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDevice.java new file mode 100644 index 0000000..955df5d --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDevice.java @@ -0,0 +1,22 @@ +package org.libsdl.app; + +import android.hardware.usb.UsbDevice; + +interface HIDDevice +{ + public int getId(); + public int getVendorId(); + public int getProductId(); + public String getSerialNumber(); + public int getVersion(); + public String getManufacturerName(); + public String getProductName(); + public UsbDevice getDevice(); + public boolean open(); + public int sendFeatureReport(byte[] report); + public int sendOutputReport(byte[] report); + public boolean getFeatureReport(byte[] report); + public void setFrozen(boolean frozen); + public void close(); + public void shutdown(); +} diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java new file mode 100644 index 0000000..3b3280b --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java @@ -0,0 +1,649 @@ +package org.libsdl.app; + +import android.content.Context; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothGattService; +import android.hardware.usb.UsbDevice; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.os.*; + +//import com.android.internal.util.HexDump; + +import java.lang.Runnable; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.UUID; + +class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice { + + private static final String TAG = "hidapi"; + private HIDDeviceManager mManager; + private BluetoothDevice mDevice; + private int mDeviceId; + private BluetoothGatt mGatt; + private boolean mIsRegistered = false; + private boolean mIsConnected = false; + private boolean mIsChromebook = false; + private boolean mIsReconnecting = false; + private boolean mFrozen = false; + private LinkedList mOperations; + GattOperation mCurrentOperation = null; + private Handler mHandler; + + private static final int TRANSPORT_AUTO = 0; + private static final int TRANSPORT_BREDR = 1; + private static final int TRANSPORT_LE = 2; + + private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000; + + static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3"); + static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3"); + static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3"); + static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 }; + + static class GattOperation { + private enum Operation { + CHR_READ, + CHR_WRITE, + ENABLE_NOTIFICATION + } + + Operation mOp; + UUID mUuid; + byte[] mValue; + BluetoothGatt mGatt; + boolean mResult = true; + + private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) { + mGatt = gatt; + mOp = operation; + mUuid = uuid; + } + + private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) { + mGatt = gatt; + mOp = operation; + mUuid = uuid; + mValue = value; + } + + public void run() { + // This is executed in main thread + BluetoothGattCharacteristic chr; + + switch (mOp) { + case CHR_READ: + chr = getCharacteristic(mUuid); + //Log.v(TAG, "Reading characteristic " + chr.getUuid()); + if (!mGatt.readCharacteristic(chr)) { + Log.e(TAG, "Unable to read characteristic " + mUuid.toString()); + mResult = false; + break; + } + mResult = true; + break; + case CHR_WRITE: + chr = getCharacteristic(mUuid); + //Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value)); + chr.setValue(mValue); + if (!mGatt.writeCharacteristic(chr)) { + Log.e(TAG, "Unable to write characteristic " + mUuid.toString()); + mResult = false; + break; + } + mResult = true; + break; + case ENABLE_NOTIFICATION: + chr = getCharacteristic(mUuid); + //Log.v(TAG, "Writing descriptor of " + chr.getUuid()); + if (chr != null) { + BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); + if (cccd != null) { + int properties = chr.getProperties(); + byte[] value; + if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { + value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; + } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) { + value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE; + } else { + Log.e(TAG, "Unable to start notifications on input characteristic"); + mResult = false; + return; + } + + mGatt.setCharacteristicNotification(chr, true); + cccd.setValue(value); + if (!mGatt.writeDescriptor(cccd)) { + Log.e(TAG, "Unable to write descriptor " + mUuid.toString()); + mResult = false; + return; + } + mResult = true; + } + } + } + } + + public boolean finish() { + return mResult; + } + + private BluetoothGattCharacteristic getCharacteristic(UUID uuid) { + BluetoothGattService valveService = mGatt.getService(steamControllerService); + if (valveService == null) + return null; + return valveService.getCharacteristic(uuid); + } + + static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) { + return new GattOperation(gatt, Operation.CHR_READ, uuid); + } + + static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) { + return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value); + } + + static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) { + return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid); + } + } + + public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) { + mManager = manager; + mDevice = device; + mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier()); + mIsRegistered = false; + mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); + mOperations = new LinkedList(); + mHandler = new Handler(Looper.getMainLooper()); + + mGatt = connectGatt(); + // final HIDDeviceBLESteamController finalThis = this; + // mHandler.postDelayed(new Runnable() { + // @Override + // public void run() { + // finalThis.checkConnectionForChromebookIssue(); + // } + // }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); + } + + public String getIdentifier() { + return String.format("SteamController.%s", mDevice.getAddress()); + } + + public BluetoothGatt getGatt() { + return mGatt; + } + + // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead + // of TRANSPORT_LE. Let's force ourselves to connect low energy. + private BluetoothGatt connectGatt(boolean managed) { + if (Build.VERSION.SDK_INT >= 23) { + try { + return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE); + } catch (Exception e) { + return mDevice.connectGatt(mManager.getContext(), managed, this); + } + } else { + return mDevice.connectGatt(mManager.getContext(), managed, this); + } + } + + private BluetoothGatt connectGatt() { + return connectGatt(false); + } + + protected int getConnectionState() { + + Context context = mManager.getContext(); + if (context == null) { + // We are lacking any context to get our Bluetooth information. We'll just assume disconnected. + return BluetoothProfile.STATE_DISCONNECTED; + } + + BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE); + if (btManager == null) { + // This device doesn't support Bluetooth. We should never be here, because how did + // we instantiate a device to start with? + return BluetoothProfile.STATE_DISCONNECTED; + } + + return btManager.getConnectionState(mDevice, BluetoothProfile.GATT); + } + + public void reconnect() { + + if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + mGatt.disconnect(); + mGatt = connectGatt(); + } + + } + + protected void checkConnectionForChromebookIssue() { + if (!mIsChromebook) { + // We only do this on Chromebooks, because otherwise it's really annoying to just attempt + // over and over. + return; + } + + int connectionState = getConnectionState(); + + switch (connectionState) { + case BluetoothProfile.STATE_CONNECTED: + if (!mIsConnected) { + // We are in the Bad Chromebook Place. We can force a disconnect + // to try to recover. + Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect."); + mIsReconnecting = true; + mGatt.disconnect(); + mGatt = connectGatt(false); + break; + } + else if (!isRegistered()) { + if (mGatt.getServices().size() > 0) { + Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover."); + probeService(this); + } + else { + Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover."); + mIsReconnecting = true; + mGatt.disconnect(); + mGatt = connectGatt(false); + break; + } + } + else { + Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!"); + return; + } + break; + + case BluetoothProfile.STATE_DISCONNECTED: + Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover."); + + mIsReconnecting = true; + mGatt.disconnect(); + mGatt = connectGatt(false); + break; + + case BluetoothProfile.STATE_CONNECTING: + Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer."); + break; + } + + final HIDDeviceBLESteamController finalThis = this; + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + finalThis.checkConnectionForChromebookIssue(); + } + }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); + } + + private boolean isRegistered() { + return mIsRegistered; + } + + private void setRegistered() { + mIsRegistered = true; + } + + private boolean probeService(HIDDeviceBLESteamController controller) { + + if (isRegistered()) { + return true; + } + + if (!mIsConnected) { + return false; + } + + Log.v(TAG, "probeService controller=" + controller); + + for (BluetoothGattService service : mGatt.getServices()) { + if (service.getUuid().equals(steamControllerService)) { + Log.v(TAG, "Found Valve steam controller service " + service.getUuid()); + + for (BluetoothGattCharacteristic chr : service.getCharacteristics()) { + if (chr.getUuid().equals(inputCharacteristic)) { + Log.v(TAG, "Found input characteristic"); + // Start notifications + BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); + if (cccd != null) { + enableNotification(chr.getUuid()); + } + } + } + return true; + } + } + + if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) { + Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us."); + mIsConnected = false; + mIsReconnecting = true; + mGatt.disconnect(); + mGatt = connectGatt(false); + } + + return false; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + private void finishCurrentGattOperation() { + GattOperation op = null; + synchronized (mOperations) { + if (mCurrentOperation != null) { + op = mCurrentOperation; + mCurrentOperation = null; + } + } + if (op != null) { + boolean result = op.finish(); // TODO: Maybe in main thread as well? + + // Our operation failed, let's add it back to the beginning of our queue. + if (!result) { + mOperations.addFirst(op); + } + } + executeNextGattOperation(); + } + + private void executeNextGattOperation() { + synchronized (mOperations) { + if (mCurrentOperation != null) + return; + + if (mOperations.isEmpty()) + return; + + mCurrentOperation = mOperations.removeFirst(); + } + + // Run in main thread + mHandler.post(new Runnable() { + @Override + public void run() { + synchronized (mOperations) { + if (mCurrentOperation == null) { + Log.e(TAG, "Current operation null in executor?"); + return; + } + + mCurrentOperation.run(); + // now wait for the GATT callback and when it comes, finish this operation + } + } + }); + } + + private void queueGattOperation(GattOperation op) { + synchronized (mOperations) { + mOperations.add(op); + } + executeNextGattOperation(); + } + + private void enableNotification(UUID chrUuid) { + GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid); + queueGattOperation(op); + } + + public void writeCharacteristic(UUID uuid, byte[] value) { + GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value); + queueGattOperation(op); + } + + public void readCharacteristic(UUID uuid) { + GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid); + queueGattOperation(op); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////// BluetoothGattCallback overridden methods + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + public void onConnectionStateChange(BluetoothGatt g, int status, int newState) { + //Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState); + mIsReconnecting = false; + if (newState == 2) { + mIsConnected = true; + // Run directly, without GattOperation + if (!isRegistered()) { + mHandler.post(new Runnable() { + @Override + public void run() { + mGatt.discoverServices(); + } + }); + } + } + else if (newState == 0) { + mIsConnected = false; + } + + // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent. + } + + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + //Log.v(TAG, "onServicesDiscovered status=" + status); + if (status == 0) { + if (gatt.getServices().size() == 0) { + Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack."); + mIsReconnecting = true; + mIsConnected = false; + gatt.disconnect(); + mGatt = connectGatt(false); + } + else { + probeService(this); + } + } + } + + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + //Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid()); + + if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) { + mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue()); + } + + finishCurrentGattOperation(); + } + + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + //Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid()); + + if (characteristic.getUuid().equals(reportCharacteristic)) { + // Only register controller with the native side once it has been fully configured + if (!isRegistered()) { + Log.v(TAG, "Registering Steam Controller with ID: " + getId()); + mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0); + setRegistered(); + } + } + + finishCurrentGattOperation(); + } + + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + // Enable this for verbose logging of controller input reports + //Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue())); + + if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) { + mManager.HIDDeviceInputReport(getId(), characteristic.getValue()); + } + } + + public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + //Log.v(TAG, "onDescriptorRead status=" + status); + } + + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + BluetoothGattCharacteristic chr = descriptor.getCharacteristic(); + //Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid()); + + if (chr.getUuid().equals(inputCharacteristic)) { + boolean hasWrittenInputDescriptor = true; + BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic); + if (reportChr != null) { + Log.v(TAG, "Writing report characteristic to enter valve mode"); + reportChr.setValue(enterValveMode); + gatt.writeCharacteristic(reportChr); + } + } + + finishCurrentGattOperation(); + } + + public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { + //Log.v(TAG, "onReliableWriteCompleted status=" + status); + } + + public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { + //Log.v(TAG, "onReadRemoteRssi status=" + status); + } + + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { + //Log.v(TAG, "onMtuChanged status=" + status); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + //////// Public API + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public int getId() { + return mDeviceId; + } + + @Override + public int getVendorId() { + // Valve Corporation + final int VALVE_USB_VID = 0x28DE; + return VALVE_USB_VID; + } + + @Override + public int getProductId() { + // We don't have an easy way to query from the Bluetooth device, but we know what it is + final int D0G_BLE2_PID = 0x1106; + return D0G_BLE2_PID; + } + + @Override + public String getSerialNumber() { + // This will be read later via feature report by Steam + return "12345"; + } + + @Override + public int getVersion() { + return 0; + } + + @Override + public String getManufacturerName() { + return "Valve Corporation"; + } + + @Override + public String getProductName() { + return "Steam Controller"; + } + + @Override + public UsbDevice getDevice() { + return null; + } + + @Override + public boolean open() { + return true; + } + + @Override + public int sendFeatureReport(byte[] report) { + if (!isRegistered()) { + Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!"); + if (mIsConnected) { + probeService(this); + } + return -1; + } + + // We need to skip the first byte, as that doesn't go over the air + byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1); + //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report)); + writeCharacteristic(reportCharacteristic, actual_report); + return report.length; + } + + @Override + public int sendOutputReport(byte[] report) { + if (!isRegistered()) { + Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!"); + if (mIsConnected) { + probeService(this); + } + return -1; + } + + //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report)); + writeCharacteristic(reportCharacteristic, report); + return report.length; + } + + @Override + public boolean getFeatureReport(byte[] report) { + if (!isRegistered()) { + Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!"); + if (mIsConnected) { + probeService(this); + } + return false; + } + + //Log.v(TAG, "getFeatureReport"); + readCharacteristic(reportCharacteristic); + return true; + } + + @Override + public void close() { + } + + @Override + public void setFrozen(boolean frozen) { + mFrozen = frozen; + } + + @Override + public void shutdown() { + close(); + + BluetoothGatt g = mGatt; + if (g != null) { + g.disconnect(); + g.close(); + mGatt = null; + } + mManager = null; + mIsRegistered = false; + mIsConnected = false; + mOperations.clear(); + } + +} diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDeviceManager.java b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDeviceManager.java new file mode 100644 index 0000000..6cc418f --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDeviceManager.java @@ -0,0 +1,685 @@ +package org.libsdl.app; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.os.Build; +import android.util.Log; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.hardware.usb.*; +import android.os.Handler; +import android.os.Looper; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +public class HIDDeviceManager { + private static final String TAG = "hidapi"; + private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION"; + + private static HIDDeviceManager sManager; + private static int sManagerRefCount = 0; + + public static HIDDeviceManager acquire(Context context) { + if (sManagerRefCount == 0) { + sManager = new HIDDeviceManager(context); + } + ++sManagerRefCount; + return sManager; + } + + public static void release(HIDDeviceManager manager) { + if (manager == sManager) { + --sManagerRefCount; + if (sManagerRefCount == 0) { + sManager.close(); + sManager = null; + } + } + } + + private Context mContext; + private HashMap mDevicesById = new HashMap(); + private HashMap mBluetoothDevices = new HashMap(); + private int mNextDeviceId = 0; + private SharedPreferences mSharedPreferences = null; + private boolean mIsChromebook = false; + private UsbManager mUsbManager; + private Handler mHandler; + private BluetoothManager mBluetoothManager; + private List mLastBluetoothDevices; + + private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + handleUsbDeviceAttached(usbDevice); + } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + handleUsbDeviceDetached(usbDevice); + } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)); + } + } + }; + + private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + // Bluetooth device was connected. If it was a Steam Controller, handle it + if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Log.d(TAG, "Bluetooth device connected: " + device); + + if (isSteamController(device)) { + connectBluetoothDevice(device); + } + } + + // Bluetooth device was disconnected, remove from controller manager (if any) + if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Log.d(TAG, "Bluetooth device disconnected: " + device); + + disconnectBluetoothDevice(device); + } + } + }; + + private HIDDeviceManager(final Context context) { + mContext = context; + + // Make sure we have the HIDAPI library loaded with the native functions + try { + SDL.loadLibrary("hidapi"); + } catch (Throwable e) { + Log.w(TAG, "Couldn't load hidapi: " + e.toString()); + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setCancelable(false); + builder.setTitle("SDL HIDAPI Error"); + builder.setMessage("Please report the following error to the SDL maintainers: " + e.getMessage()); + builder.setNegativeButton("Quit", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + try { + // If our context is an activity, exit rather than crashing when we can't + // call our native functions. + Activity activity = (Activity)context; + + activity.finish(); + } + catch (ClassCastException cce) { + // Context wasn't an activity, there's nothing we can do. Give up and return. + } + } + }); + builder.show(); + + return; + } + + HIDDeviceRegisterCallback(); + + mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE); + mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); + +// if (shouldClear) { +// SharedPreferences.Editor spedit = mSharedPreferences.edit(); +// spedit.clear(); +// spedit.commit(); +// } +// else + { + mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0); + } + + initializeUSB(); + initializeBluetooth(); + } + + public Context getContext() { + return mContext; + } + + public int getDeviceIDForIdentifier(String identifier) { + SharedPreferences.Editor spedit = mSharedPreferences.edit(); + + int result = mSharedPreferences.getInt(identifier, 0); + if (result == 0) { + result = mNextDeviceId++; + spedit.putInt("next_device_id", mNextDeviceId); + } + + spedit.putInt(identifier, result); + spedit.commit(); + return result; + } + + private void initializeUSB() { + mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE); + + /* + // Logging + for (UsbDevice device : mUsbManager.getDeviceList().values()) { + Log.i(TAG,"Path: " + device.getDeviceName()); + Log.i(TAG,"Manufacturer: " + device.getManufacturerName()); + Log.i(TAG,"Product: " + device.getProductName()); + Log.i(TAG,"ID: " + device.getDeviceId()); + Log.i(TAG,"Class: " + device.getDeviceClass()); + Log.i(TAG,"Protocol: " + device.getDeviceProtocol()); + Log.i(TAG,"Vendor ID " + device.getVendorId()); + Log.i(TAG,"Product ID: " + device.getProductId()); + Log.i(TAG,"Interface count: " + device.getInterfaceCount()); + Log.i(TAG,"---------------------------------------"); + + // Get interface details + for (int index = 0; index < device.getInterfaceCount(); index++) { + UsbInterface mUsbInterface = device.getInterface(index); + Log.i(TAG," ***** *****"); + Log.i(TAG," Interface index: " + index); + Log.i(TAG," Interface ID: " + mUsbInterface.getId()); + Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass()); + Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass()); + Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol()); + Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount()); + + // Get endpoint details + for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++) + { + UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi); + Log.i(TAG," ++++ ++++ ++++"); + Log.i(TAG," Endpoint index: " + epi); + Log.i(TAG," Attributes: " + mEndpoint.getAttributes()); + Log.i(TAG," Direction: " + mEndpoint.getDirection()); + Log.i(TAG," Number: " + mEndpoint.getEndpointNumber()); + Log.i(TAG," Interval: " + mEndpoint.getInterval()); + Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize()); + Log.i(TAG," Type: " + mEndpoint.getType()); + } + } + } + Log.i(TAG," No more devices connected."); + */ + + // Register for USB broadcasts and permission completions + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION); + mContext.registerReceiver(mUsbBroadcast, filter); + + for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) { + handleUsbDeviceAttached(usbDevice); + } + } + + UsbManager getUSBManager() { + return mUsbManager; + } + + private void shutdownUSB() { + try { + mContext.unregisterReceiver(mUsbBroadcast); + } catch (Exception e) { + // We may not have registered, that's okay + } + } + + private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) { + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) { + return true; + } + if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) { + return true; + } + return false; + } + + private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) { + final int XB360_IFACE_SUBCLASS = 93; + final int XB360_IFACE_PROTOCOL = 1; // Wired + final int XB360W_IFACE_PROTOCOL = 129; // Wireless + final int[] SUPPORTED_VENDORS = { + 0x0079, // GPD Win 2 + 0x044f, // Thrustmaster + 0x045e, // Microsoft + 0x046d, // Logitech + 0x056e, // Elecom + 0x06a3, // Saitek + 0x0738, // Mad Catz + 0x07ff, // Mad Catz + 0x0e6f, // PDP + 0x0f0d, // Hori + 0x1038, // SteelSeries + 0x11c9, // Nacon + 0x12ab, // Unknown + 0x1430, // RedOctane + 0x146b, // BigBen + 0x1532, // Razer Sabertooth + 0x15e4, // Numark + 0x162e, // Joytech + 0x1689, // Razer Onza + 0x1949, // Lab126, Inc. + 0x1bad, // Harmonix + 0x24c6, // PowerA + }; + + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && + usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS && + (usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL || + usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) { + int vendor_id = usbDevice.getVendorId(); + for (int supportedVid : SUPPORTED_VENDORS) { + if (vendor_id == supportedVid) { + return true; + } + } + } + return false; + } + + private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) { + final int XB1_IFACE_SUBCLASS = 71; + final int XB1_IFACE_PROTOCOL = 208; + final int[] SUPPORTED_VENDORS = { + 0x045e, // Microsoft + 0x0738, // Mad Catz + 0x0e6f, // PDP + 0x0f0d, // Hori + 0x1532, // Razer Wildcat + 0x24c6, // PowerA + 0x2e24, // Hyperkin + }; + + if (usbInterface.getId() == 0 && + usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && + usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS && + usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) { + int vendor_id = usbDevice.getVendorId(); + for (int supportedVid : SUPPORTED_VENDORS) { + if (vendor_id == supportedVid) { + return true; + } + } + } + return false; + } + + private void handleUsbDeviceAttached(UsbDevice usbDevice) { + connectHIDDeviceUSB(usbDevice); + } + + private void handleUsbDeviceDetached(UsbDevice usbDevice) { + List devices = new ArrayList(); + for (HIDDevice device : mDevicesById.values()) { + if (usbDevice.equals(device.getDevice())) { + devices.add(device.getId()); + } + } + for (int id : devices) { + HIDDevice device = mDevicesById.get(id); + mDevicesById.remove(id); + device.shutdown(); + HIDDeviceDisconnected(id); + } + } + + private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) { + for (HIDDevice device : mDevicesById.values()) { + if (usbDevice.equals(device.getDevice())) { + boolean opened = false; + if (permission_granted) { + opened = device.open(); + } + HIDDeviceOpenResult(device.getId(), opened); + } + } + } + + private void connectHIDDeviceUSB(UsbDevice usbDevice) { + synchronized (this) { + int interface_mask = 0; + for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) { + UsbInterface usbInterface = usbDevice.getInterface(interface_index); + if (isHIDDeviceInterface(usbDevice, usbInterface)) { + // Check to see if we've already added this interface + // This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive + int interface_id = usbInterface.getId(); + if ((interface_mask & (1 << interface_id)) != 0) { + continue; + } + interface_mask |= (1 << interface_id); + + HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index); + int id = device.getId(); + mDevicesById.put(id, device); + HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol()); + } + } + } + } + + private void initializeBluetooth() { + Log.d(TAG, "Initializing Bluetooth"); + + if (mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); + return; + } + + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18)) { + Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE"); + return; + } + + // Find bonded bluetooth controllers and create SteamControllers for them + mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE); + if (mBluetoothManager == null) { + // This device doesn't support Bluetooth. + return; + } + + BluetoothAdapter btAdapter = mBluetoothManager.getAdapter(); + if (btAdapter == null) { + // This device has Bluetooth support in the codebase, but has no available adapters. + return; + } + + // Get our bonded devices. + for (BluetoothDevice device : btAdapter.getBondedDevices()) { + + Log.d(TAG, "Bluetooth device available: " + device); + if (isSteamController(device)) { + connectBluetoothDevice(device); + } + + } + + // NOTE: These don't work on Chromebooks, to my undying dismay. + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); + filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); + mContext.registerReceiver(mBluetoothBroadcast, filter); + + if (mIsChromebook) { + mHandler = new Handler(Looper.getMainLooper()); + mLastBluetoothDevices = new ArrayList(); + + // final HIDDeviceManager finalThis = this; + // mHandler.postDelayed(new Runnable() { + // @Override + // public void run() { + // finalThis.chromebookConnectionHandler(); + // } + // }, 5000); + } + } + + private void shutdownBluetooth() { + try { + mContext.unregisterReceiver(mBluetoothBroadcast); + } catch (Exception e) { + // We may not have registered, that's okay + } + } + + // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly. + // This function provides a sort of dummy version of that, watching for changes in the + // connected devices and attempting to add controllers as things change. + public void chromebookConnectionHandler() { + if (!mIsChromebook) { + return; + } + + ArrayList disconnected = new ArrayList(); + ArrayList connected = new ArrayList(); + + List currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT); + + for (BluetoothDevice bluetoothDevice : currentConnected) { + if (!mLastBluetoothDevices.contains(bluetoothDevice)) { + connected.add(bluetoothDevice); + } + } + for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) { + if (!currentConnected.contains(bluetoothDevice)) { + disconnected.add(bluetoothDevice); + } + } + + mLastBluetoothDevices = currentConnected; + + for (BluetoothDevice bluetoothDevice : disconnected) { + disconnectBluetoothDevice(bluetoothDevice); + } + for (BluetoothDevice bluetoothDevice : connected) { + connectBluetoothDevice(bluetoothDevice); + } + + final HIDDeviceManager finalThis = this; + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + finalThis.chromebookConnectionHandler(); + } + }, 10000); + } + + public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) { + Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice); + synchronized (this) { + if (mBluetoothDevices.containsKey(bluetoothDevice)) { + Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect"); + + HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); + device.reconnect(); + + return false; + } + HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice); + int id = device.getId(); + mBluetoothDevices.put(bluetoothDevice, device); + mDevicesById.put(id, device); + + // The Steam Controller will mark itself connected once initialization is complete + } + return true; + } + + public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) { + synchronized (this) { + HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); + if (device == null) + return; + + int id = device.getId(); + mBluetoothDevices.remove(bluetoothDevice); + mDevicesById.remove(id); + device.shutdown(); + HIDDeviceDisconnected(id); + } + } + + public boolean isSteamController(BluetoothDevice bluetoothDevice) { + // Sanity check. If you pass in a null device, by definition it is never a Steam Controller. + if (bluetoothDevice == null) { + return false; + } + + // If the device has no local name, we really don't want to try an equality check against it. + if (bluetoothDevice.getName() == null) { + return false; + } + + return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0); + } + + private void close() { + shutdownUSB(); + shutdownBluetooth(); + synchronized (this) { + for (HIDDevice device : mDevicesById.values()) { + device.shutdown(); + } + mDevicesById.clear(); + mBluetoothDevices.clear(); + HIDDeviceReleaseCallback(); + } + } + + public void setFrozen(boolean frozen) { + synchronized (this) { + for (HIDDevice device : mDevicesById.values()) { + device.setFrozen(frozen); + } + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + private HIDDevice getDevice(int id) { + synchronized (this) { + HIDDevice result = mDevicesById.get(id); + if (result == null) { + Log.v(TAG, "No device for id: " + id); + Log.v(TAG, "Available devices: " + mDevicesById.keySet()); + } + return result; + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////// JNI interface functions + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + public boolean openDevice(int deviceID) { + Log.v(TAG, "openDevice deviceID=" + deviceID); + HIDDevice device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return false; + } + + // Look to see if this is a USB device and we have permission to access it + UsbDevice usbDevice = device.getDevice(); + if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) { + HIDDeviceOpenPending(deviceID); + try { + mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), 0)); + } catch (Exception e) { + Log.v(TAG, "Couldn't request permission for USB device " + usbDevice); + HIDDeviceOpenResult(deviceID, false); + } + return false; + } + + try { + return device.open(); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + return false; + } + + public int sendOutputReport(int deviceID, byte[] report) { + try { + //Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length); + HIDDevice device; + device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return -1; + } + + return device.sendOutputReport(report); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + return -1; + } + + public int sendFeatureReport(int deviceID, byte[] report) { + try { + //Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length); + HIDDevice device; + device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return -1; + } + + return device.sendFeatureReport(report); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + return -1; + } + + public boolean getFeatureReport(int deviceID, byte[] report) { + try { + //Log.v(TAG, "getFeatureReport deviceID=" + deviceID); + HIDDevice device; + device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return false; + } + + return device.getFeatureReport(report); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + return false; + } + + public void closeDevice(int deviceID) { + try { + Log.v(TAG, "closeDevice deviceID=" + deviceID); + HIDDevice device; + device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return; + } + + device.close(); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + } + + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////// Native methods + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + private native void HIDDeviceRegisterCallback(); + private native void HIDDeviceReleaseCallback(); + + native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol); + native void HIDDeviceOpenPending(int deviceID); + native void HIDDeviceOpenResult(int deviceID, boolean opened); + native void HIDDeviceDisconnected(int deviceID); + + native void HIDDeviceInputReport(int deviceID, byte[] report); + native void HIDDeviceFeatureReport(int deviceID, byte[] report); +} diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDeviceUSB.java b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDeviceUSB.java new file mode 100644 index 0000000..d20fe80 --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/HIDDeviceUSB.java @@ -0,0 +1,309 @@ +package org.libsdl.app; + +import android.hardware.usb.*; +import android.os.Build; +import android.util.Log; +import java.util.Arrays; + +class HIDDeviceUSB implements HIDDevice { + + private static final String TAG = "hidapi"; + + protected HIDDeviceManager mManager; + protected UsbDevice mDevice; + protected int mInterfaceIndex; + protected int mInterface; + protected int mDeviceId; + protected UsbDeviceConnection mConnection; + protected UsbEndpoint mInputEndpoint; + protected UsbEndpoint mOutputEndpoint; + protected InputThread mInputThread; + protected boolean mRunning; + protected boolean mFrozen; + + public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) { + mManager = manager; + mDevice = usbDevice; + mInterfaceIndex = interface_index; + mInterface = mDevice.getInterface(mInterfaceIndex).getId(); + mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier()); + mRunning = false; + } + + public String getIdentifier() { + return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex); + } + + @Override + public int getId() { + return mDeviceId; + } + + @Override + public int getVendorId() { + return mDevice.getVendorId(); + } + + @Override + public int getProductId() { + return mDevice.getProductId(); + } + + @Override + public String getSerialNumber() { + String result = null; + if (Build.VERSION.SDK_INT >= 21) { + try { + result = mDevice.getSerialNumber(); + } + catch (SecurityException exception) { + //Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage()); + } + } + if (result == null) { + result = ""; + } + return result; + } + + @Override + public int getVersion() { + return 0; + } + + @Override + public String getManufacturerName() { + String result = null; + if (Build.VERSION.SDK_INT >= 21) { + result = mDevice.getManufacturerName(); + } + if (result == null) { + result = String.format("%x", getVendorId()); + } + return result; + } + + @Override + public String getProductName() { + String result = null; + if (Build.VERSION.SDK_INT >= 21) { + result = mDevice.getProductName(); + } + if (result == null) { + result = String.format("%x", getProductId()); + } + return result; + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + public String getDeviceName() { + return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")"; + } + + @Override + public boolean open() { + mConnection = mManager.getUSBManager().openDevice(mDevice); + if (mConnection == null) { + Log.w(TAG, "Unable to open USB device " + getDeviceName()); + return false; + } + + // Force claim our interface + UsbInterface iface = mDevice.getInterface(mInterfaceIndex); + if (!mConnection.claimInterface(iface, true)) { + Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName()); + close(); + return false; + } + + // Find the endpoints + for (int j = 0; j < iface.getEndpointCount(); j++) { + UsbEndpoint endpt = iface.getEndpoint(j); + switch (endpt.getDirection()) { + case UsbConstants.USB_DIR_IN: + if (mInputEndpoint == null) { + mInputEndpoint = endpt; + } + break; + case UsbConstants.USB_DIR_OUT: + if (mOutputEndpoint == null) { + mOutputEndpoint = endpt; + } + break; + } + } + + // Make sure the required endpoints were present + if (mInputEndpoint == null || mOutputEndpoint == null) { + Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName()); + close(); + return false; + } + + // Start listening for input + mRunning = true; + mInputThread = new InputThread(); + mInputThread.start(); + + return true; + } + + @Override + public int sendFeatureReport(byte[] report) { + int res = -1; + int offset = 0; + int length = report.length; + boolean skipped_report_id = false; + byte report_number = report[0]; + + if (report_number == 0x0) { + ++offset; + --length; + skipped_report_id = true; + } + + res = mConnection.controlTransfer( + UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT, + 0x09/*HID set_report*/, + (3/*HID feature*/ << 8) | report_number, + mInterface, + report, offset, length, + 1000/*timeout millis*/); + + if (res < 0) { + Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName()); + return -1; + } + + if (skipped_report_id) { + ++length; + } + return length; + } + + @Override + public int sendOutputReport(byte[] report) { + int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000); + if (r != report.length) { + Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName()); + } + return r; + } + + @Override + public boolean getFeatureReport(byte[] report) { + int res = -1; + int offset = 0; + int length = report.length; + boolean skipped_report_id = false; + byte report_number = report[0]; + + if (report_number == 0x0) { + /* Offset the return buffer by 1, so that the report ID + will remain in byte 0. */ + ++offset; + --length; + skipped_report_id = true; + } + + res = mConnection.controlTransfer( + UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN, + 0x01/*HID get_report*/, + (3/*HID feature*/ << 8) | report_number, + mInterface, + report, offset, length, + 1000/*timeout millis*/); + + if (res < 0) { + Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName()); + return false; + } + + if (skipped_report_id) { + ++res; + ++length; + } + + byte[] data; + if (res == length) { + data = report; + } else { + data = Arrays.copyOfRange(report, 0, res); + } + mManager.HIDDeviceFeatureReport(mDeviceId, data); + + return true; + } + + @Override + public void close() { + mRunning = false; + if (mInputThread != null) { + while (mInputThread.isAlive()) { + mInputThread.interrupt(); + try { + mInputThread.join(); + } catch (InterruptedException e) { + // Keep trying until we're done + } + } + mInputThread = null; + } + if (mConnection != null) { + UsbInterface iface = mDevice.getInterface(mInterfaceIndex); + mConnection.releaseInterface(iface); + mConnection.close(); + mConnection = null; + } + } + + @Override + public void shutdown() { + close(); + mManager = null; + } + + @Override + public void setFrozen(boolean frozen) { + mFrozen = frozen; + } + + protected class InputThread extends Thread { + @Override + public void run() { + int packetSize = mInputEndpoint.getMaxPacketSize(); + byte[] packet = new byte[packetSize]; + while (mRunning) { + int r; + try + { + r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000); + } + catch (Exception e) + { + Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e); + break; + } + if (r < 0) { + // Could be a timeout or an I/O error + } + if (r > 0) { + byte[] data; + if (r == packetSize) { + data = packet; + } else { + data = Arrays.copyOfRange(packet, 0, r); + } + + if (!mFrozen) { + mManager.HIDDeviceInputReport(mDeviceId, data); + } + } + } + } + } +} diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDL.java b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDL.java new file mode 100644 index 0000000..a8ad0c6 --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDL.java @@ -0,0 +1,83 @@ +package org.libsdl.app; + +import android.content.Context; + +import java.lang.Class; +import java.lang.reflect.Method; + +/** + SDL library initialization +*/ +public class SDL { + + // This function should be called first and sets up the native code + // so it can call into the Java classes + public static void setupJNI() { + SDLActivity.nativeSetupJNI(); + SDLAudioManager.nativeSetupJNI(); + SDLControllerManager.nativeSetupJNI(); + } + + // This function should be called each time the activity is started + public static void initialize() { + SDLActivity.initialize(); + SDLAudioManager.initialize(); + SDLControllerManager.initialize(); + } + + // This function stores the current activity (SDL or not) + public static void setContext(Context context) { + mContext = context; + } + + public static Context getContext() { + return mContext; + } + + public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException { + + if (libraryName == null) { + throw new NullPointerException("No library name provided."); + } + + try { + // Let's see if we have ReLinker available in the project. This is necessary for + // some projects that have huge numbers of local libraries bundled, and thus may + // trip a bug in Android's native library loader which ReLinker works around. (If + // loadLibrary works properly, ReLinker will simply use the normal Android method + // internally.) + // + // To use ReLinker, just add it as a dependency. For more information, see + // https://github.com/KeepSafe/ReLinker for ReLinker's repository. + // + Class relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); + Class relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); + Class contextClass = mContext.getClassLoader().loadClass("android.content.Context"); + Class stringClass = mContext.getClassLoader().loadClass("java.lang.String"); + + // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if + // they've changed during updates. + Method forceMethod = relinkClass.getDeclaredMethod("force"); + Object relinkInstance = forceMethod.invoke(null); + Class relinkInstanceClass = relinkInstance.getClass(); + + // Actually load the library! + Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass); + loadMethod.invoke(relinkInstance, mContext, libraryName, null, null); + } + catch (final Throwable e) { + // Fall back + try { + System.loadLibrary(libraryName); + } + catch (final UnsatisfiedLinkError ule) { + throw ule; + } + catch (final SecurityException se) { + throw se; + } + } + } + + protected static Context mContext; +} diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDLActivity.java b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDLActivity.java new file mode 100644 index 0000000..410180f --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDLActivity.java @@ -0,0 +1,616 @@ +package org.libsdl.app; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.UiModeManager; +import android.content.ClipboardManager; +import android.content.ClipData; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.text.InputType; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.SparseArray; +import android.view.Display; +import android.view.Gravity; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.PointerIcon; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.Hashtable; +import java.util.Locale; + +/** + SDL Activity +*/ +public class SDLActivity { + private static final String TAG = "yoyo"; + + public static boolean mIsResumedCalled, mHasFocus; + + public static Locale mCurrentLocale; + + // Handle the state of the native layer + public enum NativeState { + INIT, RESUMED, PAUSED + } + + public static NativeState mNextNativeState; + public static NativeState mCurrentNativeState; + + // Main components + public static SDLActivity mSingleton; + + /** + * This method is called by SDL before loading the native shared libraries. + * It can be overridden to provide names of shared libraries to be loaded. + * The default implementation returns the defaults. It never returns null. + * An array returned by a new implementation must at least contain "SDL2". + * Also keep in mind that the order the libraries are loaded may matter. + * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). + */ + public String[] getLibraries() { + return new String[] { + "hidapi", + "SDL2", + "FAudioGMS" + }; + } + + // Load the .so + public void loadLibraries() { + for (String lib : getLibraries()) { + SDL.loadLibrary(lib); + } + } + + public static void initialize() { + // The static nature of the singleton and Android quirkyness force us to initialize everything here + // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values + mSingleton = null; + mIsResumedCalled = false; + mHasFocus = true; + mNextNativeState = NativeState.INIT; + mCurrentNativeState = NativeState.INIT; + } + + // Setup + public void onCreate(Bundle savedInstanceState) { + Log.v(TAG, "Device: " + Build.DEVICE); + Log.v(TAG, "Model: " + Build.MODEL); + Log.v(TAG, "onCreate()"); + + try { + Thread.currentThread().setName("SDLActivity"); + } catch (Exception e) { + Log.v(TAG, "modify thread properties failed " + e.toString()); + } + + // Load shared libraries + loadLibraries(); + + // Set up JNI + SDL.setupJNI(); + + // Initialize state + SDL.initialize(); + + // So we can call stuff from static callbacks + mSingleton = this; + } + + public void pauseNativeThread() { + mNextNativeState = NativeState.PAUSED; + mIsResumedCalled = false; + + SDLActivity.handleNativeState(); + } + + public void resumeNativeThread() { + mNextNativeState = NativeState.RESUMED; + mIsResumedCalled = true; + + SDLActivity.handleNativeState(); + } + + // Events + public void onPause() { + Log.v(TAG, "onPause()"); + pauseNativeThread(); + } + + public void onResume() { + Log.v(TAG, "onResume()"); + resumeNativeThread(); + } + + public void onStop() { + Log.v(TAG, "onStop()"); + pauseNativeThread(); + } + + public void onStart() { + Log.v(TAG, "onStart()"); + resumeNativeThread(); + } + + public void onWindowFocusChanged(boolean hasFocus) { + Log.v(TAG, "onWindowFocusChanged(): " + hasFocus); + + mHasFocus = hasFocus; + if (hasFocus) { + mNextNativeState = NativeState.RESUMED; + SDLActivity.handleNativeState(); + nativeFocusChanged(true); + } else { + nativeFocusChanged(false); + mNextNativeState = NativeState.PAUSED; + SDLActivity.handleNativeState(); + } + } + + public void onLowMemory() { + Log.v(TAG, "onLowMemory()"); + SDLActivity.nativeLowMemory(); + } + + public void onConfigurationChanged(Configuration newConfig) { + Log.v(TAG, "onConfigurationChanged()"); + + if (mCurrentLocale == null || !mCurrentLocale.equals(newConfig.locale)) { + mCurrentLocale = newConfig.locale; + SDLActivity.onNativeLocaleChanged(); + } + } + + public void onDestroy() { + Log.v(TAG, "onDestroy()"); + SDLActivity.nativeSendQuit(); + SDLActivity.nativeQuit(); + } + + // Called by JNI from SDL. + public static void manualBackButton() { + } + + /* Transition to next state */ + public static void handleNativeState() { + if (mNextNativeState == mCurrentNativeState) { + // Already in same state, discard. + return; + } + + // Try a transition to init state + if (mNextNativeState == NativeState.INIT) { + mCurrentNativeState = mNextNativeState; + return; + } + + // Try a transition to paused state + if (mNextNativeState == NativeState.PAUSED) { + nativePause(); + mCurrentNativeState = mNextNativeState; + Log.i("yoyo", "SDL - PAUSE!"); + return; + } + + // Try a transition to resumed state + if (mNextNativeState == NativeState.RESUMED) { + nativeResume(); + mCurrentNativeState = mNextNativeState; + Log.i("yoyo", "SDL - RESUME!"); + return; + } + } + + /** + * This method is called by SDL if SDL did not handle a message itself. + * This happens if a received message contains an unsupported command. + * Method can be overwritten to handle Messages in a different class. + * @param command the command of the message. + * @param param the parameter of the message. May be null. + * @return if the message was handled in overridden method. + */ + public boolean onUnhandledMessage(int command, Object param) { + return false; + } + + /** + * A Handler class for Messages from native SDL applications. + * It uses current Activities as target (e.g. for the title). + * static to prevent implicit references to enclosing object. + */ + public static class SDLCommandHandler extends Handler { + @Override + public void handleMessage(Message msg) { + } + } + + // Handler for the messages + Handler commandHandler = new SDLCommandHandler(); + + // Send a message from the SDLMain thread + boolean sendCommand(int command, Object data) { + Message msg = commandHandler.obtainMessage(); + msg.arg1 = command; + msg.obj = data; + boolean result = commandHandler.sendMessage(msg); + + return result; + } + + // C functions we call + public static native int nativeSetupJNI(); + public static native int nativeRunMain(String library, String function, Object arguments); + public static native void nativeLowMemory(); + public static native void nativeSendQuit(); + public static native void nativeQuit(); + public static native void nativePause(); + public static native void nativeResume(); + public static native void nativeFocusChanged(boolean hasFocus); + public static native void onNativeDropFile(String filename); + public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float rate); + public static native void onNativeResize(); + public static native void onNativeKeyDown(int keycode); + public static native void onNativeKeyUp(int keycode); + public static native boolean onNativeSoftReturnKey(); + public static native void onNativeKeyboardFocusLost(); + public static native void onNativeMouse(int button, int action, float x, float y, boolean relative); + public static native void onNativeTouch(int touchDevId, int pointerFingerId, + int action, float x, + float y, float p); + public static native void onNativeAccel(float x, float y, float z); + public static native void onNativeClipboardChanged(); + public static native void onNativeSurfaceCreated(); + public static native void onNativeSurfaceChanged(); + public static native void onNativeSurfaceDestroyed(); + public static native String nativeGetHint(String name); + public static native void nativeSetenv(String name, String value); + public static native void onNativeOrientationChanged(int orientation); + public static native void nativeAddTouch(int touchId, String name); + public static native void nativePermissionResult(int requestCode, boolean result); + public static native void onNativeLocaleChanged(); + + /** + * This method is called by SDL using JNI. + */ + public static boolean setActivityTitle(String title) { + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static void setWindowStyle(boolean fullscreen) { + } + + /** + * This method is called by SDL using JNI. + * This is a static method for JNI convenience, it calls a non-static method + * so that is can be overridden + */ + public static void setOrientation(int w, int h, boolean resizable, String hint){ + } + + /** + * This can be overridden + */ + public void setOrientationBis(int w, int h, boolean resizable, String hint){ + } + + /** + * This method is called by SDL using JNI. + */ + public static void minimizeWindow() { + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean shouldMinimizeOnFocusLoss() { + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isScreenKeyboardShown(){ + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean supportsRelativeMouse(){ + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean setRelativeMouseEnabled(boolean enabled) { + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean sendMessage(int command, int param) { + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static Context getContext() { + return SDL.getContext(); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isAndroidTV() { + return false; + } + + public static double getDiagonal() { + return 0.0; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isTablet() { + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isChromebook() { + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isDeXMode() { + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static DisplayMetrics getDisplayDPI() { + return getContext().getResources().getDisplayMetrics(); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean getManifestEnvironmentVariables() { + Log.i("yoyo", "sdl envvars req!"); + nativeSetenv("SDL_ANDROID_BLOCK_ON_PAUSE", "1"); + nativeSetenv("SDL_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO", "1"); + Log.i("yoyo", "sdl envvars ok!"); + + return true; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean showTextInput(int x, int y, int w, int h) { + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static Surface getNativeSurface() { + return null; + } + + // Input + + /** + * This method is called by SDL using JNI. + */ + public static void initTouch() { + } + + // Messagebox + + /** Result of current messagebox. Also used for blocking the calling thread. */ + public final int[] messageboxSelection = new int[1]; + + /** + * This method is called by SDL using JNI. + * Shows the messagebox from UI thread and block calling thread. + * buttonFlags, buttonIds and buttonTexts must have same length. + * @param buttonFlags array containing flags for every button. + * @param buttonIds array containing id for every button. + * @param buttonTexts array containing text for every button. + * @param colors null for default or array of length 5 containing colors. + * @return button id or -1. + */ + public int messageboxShowMessageBox( + final int flags, + final String title, + final String message, + final int[] buttonFlags, + final int[] buttonIds, + final String[] buttonTexts, + final int[] colors) { + return -1; + } + + public void messageboxCreateAndShow(Bundle args) { + } + + private final Runnable rehideSystemUi = new Runnable() { + @Override + public void run() { + } + }; + + public void onSystemUiVisibilityChange(int visibility) { + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean clipboardHasText() { + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static String clipboardGetText() { + return ""; + } + + /** + * This method is called by SDL using JNI. + */ + public static void clipboardSetText(String string) { + } + + /** + * This method is called by SDL using JNI. + */ + public static int createCustomCursor(int[] colors, int width, int height, int hotSpotX, int hotSpotY) { + return 0; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean setCustomCursor(int cursorID) { + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean setSystemCursor(int cursorID) { + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static void requestPermission(String permission, int requestCode) { + if (Build.VERSION.SDK_INT < 23) { + nativePermissionResult(requestCode, true); + return; + } + + Activity activity = (Activity)getContext(); + if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[]{permission}, requestCode); + } else { + nativePermissionResult(requestCode, true); + } + } + + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + boolean result = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED); + nativePermissionResult(requestCode, result); + } + + /** + * This method is called by SDL using JNI. + */ + public static int openURL(String url) { + return -1; + } + + /** + * This method is called by SDL using JNI. + */ + public static int showToast(String message, int duration, int gravity, int xOffset, int yOffset) { + return -1; + } +} + +/** + Simple runnable to start the SDL application +*/ +class SDLMain implements Runnable { + @Override + public void run() { + try { + android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_DISPLAY); + } catch (Exception e) { + Log.v("SDL", "modify thread properties failed " + e.toString()); + } + } +} + +class SDLInputConnection extends BaseInputConnection { + public SDLInputConnection(View targetView, boolean fullEditor) { + super(targetView, fullEditor); + + } + + public boolean sendKeyEvent(KeyEvent event) { + return false; + } + + public boolean commitText(CharSequence text, int newCursorPosition) { + return false; + } + + public boolean setComposingText(CharSequence text, int newCursorPosition) { + return false; + } + + public static native void nativeCommitText(String text, int newCursorPosition); + + public native void nativeGenerateScancodeForUnichar(char c); + + public native void nativeSetComposingText(String text, int newCursorPosition); + + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + return false; + } +} diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDLAudioManager.java b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDLAudioManager.java new file mode 100644 index 0000000..6b512e2 --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDLAudioManager.java @@ -0,0 +1,390 @@ +package org.libsdl.app; + +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioRecord; +import android.media.AudioTrack; +import android.media.MediaRecorder; +import android.os.Build; +import android.util.Log; + +public class SDLAudioManager +{ + protected static final String TAG = "yoyo"; + + protected static AudioTrack mAudioTrack; + protected static AudioRecord mAudioRecord; + + public static void initialize() { + mAudioTrack = null; + mAudioRecord = null; + } + + // Audio + + protected static String getAudioFormatString(int audioFormat) { + switch (audioFormat) { + case AudioFormat.ENCODING_PCM_8BIT: + return "8-bit"; + case AudioFormat.ENCODING_PCM_16BIT: + return "16-bit"; + case AudioFormat.ENCODING_PCM_FLOAT: + return "float"; + default: + return Integer.toString(audioFormat); + } + } + + protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) { + int channelConfig; + int sampleSize; + int frameSize; + + Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz"); + + /* On older devices let's use known good settings */ + if (Build.VERSION.SDK_INT < 21) { + if (desiredChannels > 2) { + desiredChannels = 2; + } + if (sampleRate < 8000) { + sampleRate = 8000; + } else if (sampleRate > 48000) { + sampleRate = 48000; + } + } + + if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) { + int minSDKVersion = (isCapture ? 23 : 21); + if (Build.VERSION.SDK_INT < minSDKVersion) { + audioFormat = AudioFormat.ENCODING_PCM_16BIT; + } + } + switch (audioFormat) + { + case AudioFormat.ENCODING_PCM_8BIT: + sampleSize = 1; + break; + case AudioFormat.ENCODING_PCM_16BIT: + sampleSize = 2; + break; + case AudioFormat.ENCODING_PCM_FLOAT: + sampleSize = 4; + break; + default: + Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT"); + audioFormat = AudioFormat.ENCODING_PCM_16BIT; + sampleSize = 2; + break; + } + + if (isCapture) { + switch (desiredChannels) { + case 1: + channelConfig = AudioFormat.CHANNEL_IN_MONO; + break; + case 2: + channelConfig = AudioFormat.CHANNEL_IN_STEREO; + break; + default: + Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo"); + desiredChannels = 2; + channelConfig = AudioFormat.CHANNEL_IN_STEREO; + break; + } + } else { + switch (desiredChannels) { + case 1: + channelConfig = AudioFormat.CHANNEL_OUT_MONO; + break; + case 2: + channelConfig = AudioFormat.CHANNEL_OUT_STEREO; + break; + case 3: + channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER; + break; + case 4: + channelConfig = AudioFormat.CHANNEL_OUT_QUAD; + break; + case 5: + channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER; + break; + case 6: + channelConfig = AudioFormat.CHANNEL_OUT_5POINT1; + break; + case 7: + channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER; + break; + case 8: + if (Build.VERSION.SDK_INT >= 23) { + channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; + } else { + Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround"); + desiredChannels = 6; + channelConfig = AudioFormat.CHANNEL_OUT_5POINT1; + } + break; + default: + Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo"); + desiredChannels = 2; + channelConfig = AudioFormat.CHANNEL_OUT_STEREO; + break; + } + +/* + Log.v(TAG, "Speaker configuration (and order of channels):"); + + if ((channelConfig & 0x00000004) != 0) { + Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT"); + } + if ((channelConfig & 0x00000008) != 0) { + Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT"); + } + if ((channelConfig & 0x00000010) != 0) { + Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER"); + } + if ((channelConfig & 0x00000020) != 0) { + Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY"); + } + if ((channelConfig & 0x00000040) != 0) { + Log.v(TAG, " CHANNEL_OUT_BACK_LEFT"); + } + if ((channelConfig & 0x00000080) != 0) { + Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT"); + } + if ((channelConfig & 0x00000100) != 0) { + Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER"); + } + if ((channelConfig & 0x00000200) != 0) { + Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER"); + } + if ((channelConfig & 0x00000400) != 0) { + Log.v(TAG, " CHANNEL_OUT_BACK_CENTER"); + } + if ((channelConfig & 0x00000800) != 0) { + Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT"); + } + if ((channelConfig & 0x00001000) != 0) { + Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT"); + } +*/ + } + frameSize = (sampleSize * desiredChannels); + + // Let the user pick a larger buffer if they really want -- but ye + // gods they probably shouldn't, the minimums are horrifyingly high + // latency already + int minBufferSize; + if (isCapture) { + minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat); + } else { + minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat); + } + desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize); + + int[] results = new int[4]; + + if (isCapture) { + if (mAudioRecord == null) { + mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate, + channelConfig, audioFormat, desiredFrames * frameSize); + + // see notes about AudioTrack state in audioOpen(), above. Probably also applies here. + if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) { + Log.e(TAG, "Failed during initialization of AudioRecord"); + mAudioRecord.release(); + mAudioRecord = null; + return null; + } + + mAudioRecord.startRecording(); + } + + results[0] = mAudioRecord.getSampleRate(); + results[1] = mAudioRecord.getAudioFormat(); + results[2] = mAudioRecord.getChannelCount(); + + } else { + if (mAudioTrack == null) { + mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); + + // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid + // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java + // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() + if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { + /* Try again, with safer values */ + + Log.e(TAG, "Failed during initialization of Audio Track"); + mAudioTrack.release(); + mAudioTrack = null; + return null; + } + + mAudioTrack.play(); + } + + results[0] = mAudioTrack.getSampleRate(); + results[1] = mAudioTrack.getAudioFormat(); + results[2] = mAudioTrack.getChannelCount(); + } + results[3] = desiredFrames; + + Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz"); + + return results; + } + + /** + * This method is called by SDL using JNI. + */ + public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) { + return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames); + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteFloatBuffer(float[] buffer) { + if (mAudioTrack == null) { + Log.e(TAG, "Attempted to make audio call with uninitialized audio!"); + return; + } + + for (int i = 0; i < buffer.length;) { + int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w(TAG, "SDL audio: error return from write(float)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteShortBuffer(short[] buffer) { + if (mAudioTrack == null) { + Log.e(TAG, "Attempted to make audio call with uninitialized audio!"); + return; + } + + for (int i = 0; i < buffer.length;) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w(TAG, "SDL audio: error return from write(short)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteByteBuffer(byte[] buffer) { + if (mAudioTrack == null) { + Log.e(TAG, "Attempted to make audio call with uninitialized audio!"); + return; + } + + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w(TAG, "SDL audio: error return from write(byte)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) { + return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames); + } + + /** This method is called by SDL using JNI. */ + public static int captureReadFloatBuffer(float[] buffer, boolean blocking) { + return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); + } + + /** This method is called by SDL using JNI. */ + public static int captureReadShortBuffer(short[] buffer, boolean blocking) { + if (Build.VERSION.SDK_INT < 23) { + return mAudioRecord.read(buffer, 0, buffer.length); + } else { + return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); + } + } + + /** This method is called by SDL using JNI. */ + public static int captureReadByteBuffer(byte[] buffer, boolean blocking) { + if (Build.VERSION.SDK_INT < 23) { + return mAudioRecord.read(buffer, 0, buffer.length); + } else { + return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); + } + } + + /** This method is called by SDL using JNI. */ + public static void audioClose() { + if (mAudioTrack != null) { + mAudioTrack.stop(); + mAudioTrack.release(); + mAudioTrack = null; + } + } + + /** This method is called by SDL using JNI. */ + public static void captureClose() { + if (mAudioRecord != null) { + mAudioRecord.stop(); + mAudioRecord.release(); + mAudioRecord = null; + } + } + + /** This method is called by SDL using JNI. */ + public static void audioSetThreadPriority(boolean iscapture, int device_id) { + try { + + /* Set thread name */ + if (iscapture) { + Thread.currentThread().setName("SDLAudioC" + device_id); + } else { + Thread.currentThread().setName("SDLAudioP" + device_id); + } + + /* Set thread priority */ + android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO); + + } catch (Exception e) { + Log.v(TAG, "modify thread properties failed " + e.toString()); + } + } + + public static native int nativeSetupJNI(); +} diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDLControllerManager.java b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDLControllerManager.java new file mode 100644 index 0000000..c8cf891 --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/libsdl/app/SDLControllerManager.java @@ -0,0 +1,92 @@ +package org.libsdl.app; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import android.content.Context; +import android.os.Build; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.util.Log; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; + + +public class SDLControllerManager { + + public static native int nativeSetupJNI(); + + public static native int nativeAddJoystick(int device_id, String name, String desc, + int vendor_id, int product_id, + boolean is_accelerometer, int button_mask, + int naxes, int nhats, int nballs); + public static native int nativeRemoveJoystick(int device_id); + public static native int nativeAddHaptic(int device_id, String name); + public static native int nativeRemoveHaptic(int device_id); + public static native int onNativePadDown(int device_id, int keycode); + public static native int onNativePadUp(int device_id, int keycode); + public static native void onNativeJoy(int device_id, int axis, + float value); + public static native void onNativeHat(int device_id, int hat_id, + int x, int y); + + private static final String TAG = "SDLControllerManager"; + + public static void initialize() { + } + + // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance + public static boolean handleJoystickMotionEvent(MotionEvent event) { + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static void pollInputDevices() { + } + + /** + * This method is called by SDL using JNI. + */ + public static void pollHapticDevices() { + } + + /** + * This method is called by SDL using JNI. + */ + public static void hapticRun(int device_id, float intensity, int length) { + } + + /** + * This method is called by SDL using JNI. + */ + public static void hapticStop(int device_id){ + } + + // Check if a given device is considered a possible SDL joystick + public static boolean isDeviceSDLJoystick(int deviceId) { + return false; + } +} + +class SDLJoystickHandler { + /** + * Handles given MotionEvent. + * @param event the event to be handled. + * @return if given event was processed. + */ + public boolean handleMotionEvent(MotionEvent event) { + return false; + } + + /** + * Handles adding and removing of input devices. + */ + public void pollInputDevices() { + } +} diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/screwyoyo/faudiogms/FAudioGMSNative.java b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/screwyoyo/faudiogms/FAudioGMSNative.java new file mode 100644 index 0000000..73578d4 --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/Sdk/org.screwyoyo.faudiogms/src/main/java/org/screwyoyo/faudiogms/FAudioGMSNative.java @@ -0,0 +1,81 @@ +package org.screwyoyo.faudiogms; + +import java.lang.String; + +public class FAudioGMSNative +{ + public FAudioGMSNative() + { + super(); + } + + /* exactly as in FAudioGMS_JNI.c: */ + public native double FAudioGMS_Init(double spatialDistanceScale, double timestep); + + public native double FAudioGMS_StaticSound_LoadWAV(String filePath); + public native double FAudioGMS_StaticSound_CreateSoundInstance(double staticSoundID); + public native double FAudioGMS_StaticSound_Destroy(double staticSoundID); + + public native double FAudioGMS_StreamingSound_LoadOGG(String filepath); + + public native double FAudioGMS_SoundInstance_Play(double soundInstanceID, double loop); + public native double FAudioGMS_SoundInstance_Pause(double soundInstanceID); + public native double FAudioGMS_SoundInstance_Stop(double soundInstanceID); + + public native double FAudioGMS_SoundInstance_SetPan(double soundInstanceID, double pan); + public native double FAudioGMS_SoundInstance_SetPitch(double soundInstanceID, double pitch); + public native double FAudioGMS_SoundInstance_SetVolume(double soundInstanceID, double volume); + public native double FAudioGMS_SoundInstance_Set3DPosition(double soundInstanceID, double x, double y, double z); + public native double FAudioGMS_SoundInstance_Set3DVelocity(double soundInstanceID, double xVelocity, double yVelocity, double zVelocity); + public native double FAudioGMS_SoundInstance_SetTrackPositionInSeconds(double soundInstanceID, double trackPositionInSeconds); + public native double FAudioGMS_SoundInstance_SetVolumeOverTime(double soundInstanceID, double volume, double milliseconds); + public native double FAudioGMS_SoundInstance_SetLowPassFilter(double soundInstanceID, double lowPassFilter, double Q); + public native double FAudioGMS_SoundInstance_SetHighPassFilter(double soundInstanceID, double highPassFilter, double Q); + public native double FAudioGMS_SoundInstance_SetBandPassFilter(double soundInstanceID, double bandPassFilter, double Q); + + public native double FAudioGMS_SoundInstance_GetPitch(double soundInstanceID); + public native double FAudioGMS_SoundInstance_GetVolume(double soundInstanceID); + public native double FAudioGMS_SoundInstance_GetTrackLengthInSeconds(double soundInstanceID); + public native double FAudioGMS_SoundInstance_GetTrackPositionInSeconds(double soundInstanceID); + + public native double FAudioGMS_SoundInstance_Destroy(double soundInstanceID); + public native double FAudioGMS_SoundInstance_DestroyWhenFinished(double soundInstanceID); + + public native double FAudioGMS_EffectChain_Create(); + public native double FAudioGMS_EffectChain_AddDefaultReverb(double effectChainID); + public native double FAudioGMS_EffectChain_AddReverb( + double effectChainID, + double wetDryMix, + double reflectionsDelay, + double reverbDelay, + double earlyDiffusion, + double lateDiffusion, + double lowEQGain, + double lowEQCutoff, + double highEQGain, + double highEQCutoff, + double reflectionsGain, + double reverbGain, + double decayTime, + double density, + double roomSize + ); + public native double FAudioGMS_EffectChain_Destroy(double effectChainID); + + /* + * NOTE: Any changes to the effect chain will NOT apply after this is set! + * You MUST call SetEffectChain again if you make changes to the effect chain parameters! + */ + public native double FAudioGMS_SoundInstance_SetEffectChain(double soundInstanceID, double effectChainID, double effectGain); + public native double FAudioGMS_SoundInstance_SetEffectGain(double soundInstanceID, double effectGain); + + public native double FAudioGMS_SetListenerPosition(double x, double y, double z); + public native double FAudioGMS_SetListenerVelocity(double xVelocity, double yVelocity, double zVelocity); + + public native double FAudioGMS_PauseAll(); /* mobile platforms, man... */ + public native double FAudioGMS_ResumeAll(); /* same thing here */ + public native double FAudioGMS_StopAll(); + + public native double FAudioGMS_Update(); + public native double FAudioGMS_Destroy(); +} diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/libs/.gitkeep b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libFAudioGMS.so b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libFAudioGMS.so new file mode 100644 index 0000000..2542d60 --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libFAudioGMS.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c3948edd7499c2e558300a1b90f6f5e5e1cb486d80f9960c577f2cf663a26a7 +size 279904 diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libSDL2.so b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libSDL2.so new file mode 100644 index 0000000..0c09659 --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libSDL2.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91f127491500ca94a8eda76a9d73d6d9f80b745bce9f37ee813791828764e908 +size 1450728 diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libhidapi.so b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libhidapi.so new file mode 100644 index 0000000..9b58ab1 --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libhidapi.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cbdc467efc00791248d70728d0517c2db318e70fe68bb3f9e43b73136e1d4b6 +size 24280 diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libFAudioGMS.so b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libFAudioGMS.so new file mode 100644 index 0000000..f4e81cc --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libFAudioGMS.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd28bf5f21d15166074ec2c817dd72f2f81a8507326615b1788a1a8c62669a69 +size 178060 diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libSDL2.so b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libSDL2.so new file mode 100644 index 0000000..c18c4ec --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libSDL2.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b39fa8542bb0d4258bbdcdc07c816e5dee484621e813c683db5761ba3e0952c +size 1029388 diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libhidapi.so b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libhidapi.so new file mode 100644 index 0000000..6e146cc --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libhidapi.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:320bae7e28e21dd64c40083bc9036423661341a35b0803f8018f3ef5f7c5c248 +size 17236 diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libFAudioGMS.so b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libFAudioGMS.so new file mode 100644 index 0000000..994cf9b --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libFAudioGMS.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8535e4f92e1d7c0d56efac9e94bf8899b3becd23c1ac7351169ef7328651f419 +size 328360 diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libSDL2.so b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libSDL2.so new file mode 100644 index 0000000..0eaec5d --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libSDL2.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:826f98889b6e3d36be70c06699fc1f4f109a92df478fa95fe128b9bc81f29c66 +size 1684220 diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libhidapi.so b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libhidapi.so new file mode 100644 index 0000000..b29ecc4 --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libhidapi.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab221fc8a0de79cb175c33b240492b77a954a1286716359101273a31b6b69c14 +size 22424 diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libFAudioGMS.so b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libFAudioGMS.so new file mode 100644 index 0000000..a09a738 --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libFAudioGMS.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b761e89f48c47093f5aaa62d78f319712a3d1e9bcfd21115824b89c38c77c659 +size 319784 diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libSDL2.so b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libSDL2.so new file mode 100644 index 0000000..4de4d9f --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libSDL2.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:923239b6a0353e1c105de66504ddc0d93a5c6554974cc6a513268a8bf652ccce +size 1660944 diff --git a/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libhidapi.so b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libhidapi.so new file mode 100644 index 0000000..a50fbfa --- /dev/null +++ b/gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libhidapi.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f750ac957990db9493a7cad6caf8cff7f9046b540ac41ca592584f571edb0ffd +size 25944 diff --git a/gamemaker/extensions/FAudioGMS/FAudioGMS.yy b/gamemaker/extensions/FAudioGMS/FAudioGMS.yy index ede25de..053ddc9 100644 --- a/gamemaker/extensions/FAudioGMS/FAudioGMS.yy +++ b/gamemaker/extensions/FAudioGMS/FAudioGMS.yy @@ -3,17 +3,17 @@ "options": [], "exportToGame": true, "supportedTargets": -1, - "extensionVersion": "0.2.0", + "extensionVersion": "0.3.0", "packageId": "", "productId": "", "author": "", - "date": "2021-10-21T16:46:44.6241287-07:00", + "date": "2021-10-22T04:46:44.6241287+05:00", "license": "", "description": "", "helpfile": "", "iosProps": false, "tvosProps": false, - "androidProps": false, + "androidProps": true, "installdir": "", "files": [ {"filename":"FAudioGMS.dll","origname":"","init":"","final":"","kind":1,"uncompress":false,"functions":[ @@ -160,7 +160,8 @@ ],"constants":[],"ProxyFiles":[ {"TargetMask":7,"resourceVersion":"1.0","name":"libFAudioGMS.so","tags":[],"resourceType":"GMProxyFile",}, {"TargetMask":7,"resourceVersion":"1.0","name":"libSDL2.so","tags":[],"resourceType":"GMProxyFile",}, - ],"copyToTargets":192,"order":[ + {"TargetMask":3,"resourceVersion":"1.0","name":"FAudioGMSAndroidDummy.ext","tags":[],"resourceType":"GMProxyFile",}, + ],"copyToTargets":200,"order":[ {"name":"FAudioGMS_Init","path":"extensions/FAudioGMS/FAudioGMS.yy",}, {"name":"FAudioGMS_StaticSound_LoadWAV","path":"extensions/FAudioGMS/FAudioGMS.yy",}, {"name":"FAudioGMS_StaticSound_CreateSoundInstance","path":"extensions/FAudioGMS/FAudioGMS.yy",}, @@ -204,7 +205,7 @@ "tvosclassname": null, "tvosdelegatename": null, "iosdelegatename": "", - "androidclassname": "", + "androidclassname": "FAudioGMSBridge", "sourcedir": "", "androidsourcedir": "", "macsourcedir": "", @@ -228,7 +229,7 @@ "tvosThirdPartyFrameworkEntries": [], "IncludedResources": [], "androidPermissions": [], - "copyToTargets": 192, + "copyToTargets": 200, "iosCocoaPods": "", "tvosCocoaPods": "", "iosCocoaPodDependencies": "", @@ -241,4 +242,4 @@ "name": "FAudioGMS", "tags": [], "resourceType": "GMExtension", -} \ No newline at end of file +} diff --git a/gamemaker/extensions/FAudioGMS/FAudioGMSAndroidDummy.ext b/gamemaker/extensions/FAudioGMS/FAudioGMSAndroidDummy.ext new file mode 100644 index 0000000..e69de29 diff --git a/gamemaker/scripts/FAudioGMS_Scripts/FAudioGMS_Scripts.gml b/gamemaker/scripts/FAudioGMS_Scripts/FAudioGMS_Scripts.gml index 28234e3..aa6781f 100644 --- a/gamemaker/scripts/FAudioGMS_Scripts/FAudioGMS_Scripts.gml +++ b/gamemaker/scripts/FAudioGMS_Scripts/FAudioGMS_Scripts.gml @@ -1,9 +1,16 @@ +// working_directory on android is "assets/" which makes SDL freak out. +function GetPathPrepend() +{ + if (os_type != os_android) return working_directory; + else return ""; +} + // StaticSounds are usually short and intended to be played multiple times. // All of the sound data lives in memory for as long as the StaticSound exists. // Playing a StaticSound returns a SoundInstance. function LoadStaticSound(filename) { - var filePath = working_directory + "audio/static/" + filename; + var filePath = GetPathPrepend() + "audio/static/" + filename; var staticSoundID = FAudioGMS_StaticSound_LoadWAV(filePath); return new StaticSound(staticSoundID); } @@ -69,7 +76,7 @@ function StaticSound(_staticSoundID) constructor // Note that StreamingSounds are SoundInstances. function LoadStreamingSound(filename) { - var filePath = working_directory + "audio/streaming/" + filename; + var filePath = GetPathPrepend() + "audio/streaming/" + filename; soundInstanceID = FAudioGMS_StreamingSound_LoadOGG(filePath); return new SoundInstance(soundInstanceID); }