android support
							parent
							
								
									30e4f01d37
								
							
						
					
					
						commit
						3f83d2a825
					
				|  | @ -2,3 +2,4 @@ | |||
| .vs | ||||
| visualc/x64 | ||||
| build/ | ||||
| android/buildandroid/ | ||||
|  |  | |||
|  | @ -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) | ||||
|  | @ -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 <evan@moonside.games> | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| /* These are the Native -> JNI conv wrappers, they must only be built for Android */ | ||||
| #ifdef __ANDROID__ | ||||
| 
 | ||||
| #include <jni.h> | ||||
| #include <FAudioGMS.h> | ||||
| 
 | ||||
| /*
 | ||||
| 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... */ | ||||
|  | @ -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. | ||||
|  | @ -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;} | ||||
| } | ||||
|  | @ -0,0 +1,15 @@ | |||
| plugins { | ||||
|     id 'com.android.library' | ||||
| } | ||||
| 
 | ||||
| android { | ||||
|     compileSdkVersion 28 | ||||
| } | ||||
| 
 | ||||
| repositories { | ||||
|     mavenCentral() | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     compile 'com.getkeepsafe.relinker:relinker:1.4.4' | ||||
| } | ||||
|  | @ -0,0 +1,3 @@ | |||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="org.screwyoyo.faudiogms"> | ||||
| </manifest> | ||||
|  | @ -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(); | ||||
| } | ||||
|  | @ -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<GattOperation> 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<GattOperation>(); | ||||
|         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(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>(); | ||||
|     private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>(); | ||||
|     private int mNextDeviceId = 0; | ||||
|     private SharedPreferences mSharedPreferences = null; | ||||
|     private boolean mIsChromebook = false; | ||||
|     private UsbManager mUsbManager; | ||||
|     private Handler mHandler; | ||||
|     private BluetoothManager mBluetoothManager; | ||||
|     private List<BluetoothDevice> 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<Integer> devices = new ArrayList<Integer>(); | ||||
|         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<BluetoothDevice>(); | ||||
| 
 | ||||
|             // 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<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>(); | ||||
|         ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>(); | ||||
| 
 | ||||
|         List<BluetoothDevice> 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); | ||||
| } | ||||
|  | @ -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); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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; | ||||
| } | ||||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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(); | ||||
| } | ||||
|  | @ -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() { | ||||
|     } | ||||
| } | ||||
|  | @ -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(); | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libFAudioGMS.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
							
						
						
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libFAudioGMS.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libSDL2.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
							
						
						
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libSDL2.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libhidapi.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
							
						
						
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/arm64-v8a/libhidapi.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libFAudioGMS.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
							
						
						
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libFAudioGMS.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libSDL2.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
							
						
						
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libSDL2.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libhidapi.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
							
						
						
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/armeabi-v7a/libhidapi.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libFAudioGMS.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
							
						
						
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libFAudioGMS.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libSDL2.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
							
						
						
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libSDL2.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libhidapi.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
							
						
						
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86/libhidapi.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libFAudioGMS.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
							
						
						
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libFAudioGMS.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libSDL2.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
							
						
						
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libSDL2.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libhidapi.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
							
						
						
									
										
											BIN
										
									
								
								gamemaker/extensions/FAudioGMS/AndroidSource/libs/x86_64/libhidapi.so (Stored with Git LFS)
								
								
								
								
									Normal file
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -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": "", | ||||
|  |  | |||
|  | @ -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); | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue