diff --git a/build/android/src/org/libsdl/app/SDL.java b/build/android/src/org/libsdl/app/SDL.java deleted file mode 100644 index cfe4830..0000000 --- a/build/android/src/org/libsdl/app/SDL.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.libsdl.app; - -import android.content.Context; - -/** - 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() { - setContext(null); - - 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; - } - - protected static Context mContext; -} diff --git a/build/android/src/org/libsdl/app/SDLActivity.java b/build/android/src/org/libsdl/app/SDLActivity.java index 9119473..c3d4484 100644 --- a/build/android/src/org/libsdl/app/SDLActivity.java +++ b/build/android/src/org/libsdl/app/SDLActivity.java @@ -2,9 +2,12 @@ package org.libsdl.app; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; import java.lang.reflect.Method; -import java.util.Objects; import android.app.*; import android.content.*; @@ -23,6 +26,7 @@ import android.util.Log; import android.util.SparseArray; import android.graphics.*; import android.graphics.drawable.Drawable; +import android.media.*; import android.hardware.*; import android.content.pm.ActivityInfo; @@ -32,16 +36,8 @@ import android.content.pm.ActivityInfo; public class SDLActivity extends Activity { private static final String TAG = "SDL"; - public static boolean mIsResumedCalled, mIsSurfaceReady, mHasFocus; - - // Handle the state of the native layer - public enum NativeState { - INIT, RESUMED, PAUSED - } - - public static NativeState mNextNativeState; - public static NativeState mCurrentNativeState; - + // Keep track of the paused state + public static boolean mIsPaused, mIsSurfaceReady, mHasFocus; public static boolean mExitCalledFromJava; /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ @@ -55,36 +51,15 @@ public class SDLActivity extends Activity { protected static SDLActivity mSingleton; protected static SDLSurface mSurface; protected static View mTextEdit; - protected static boolean mScreenKeyboardShown; protected static ViewGroup mLayout; - protected static SDLClipboardHandler mClipboardHandler; - + protected static SDLJoystickHandler mJoystickHandler; // This is what SDL runs in. It invokes SDL_main(), eventually protected static Thread mSDLThread; - /** - * This method returns the name of the shared object with the application entry point - * It can be overridden by derived classes. - */ - protected String getMainSharedObject() { - String library; - String[] libraries = SDLActivity.mSingleton.getLibraries(); - if (libraries.length > 0) { - library = "lib" + libraries[libraries.length - 1] + ".so"; - } else { - library = "libmain.so"; - } - return library; - } - - /** - * This method returns the name of the application entry point - * It can be overridden by derived classes. - */ - protected String getMainFunction() { - return "SDL_main"; - } + // Audio + protected static AudioTrack mAudioTrack; + protected static AudioRecord mAudioRecord; /** * This method is called by SDL before loading the native shared libraries. @@ -129,15 +104,15 @@ public class SDLActivity extends Activity { mSurface = null; mTextEdit = null; mLayout = null; - mClipboardHandler = null; + mJoystickHandler = null; mSDLThread = null; + mAudioTrack = null; + mAudioRecord = null; mExitCalledFromJava = false; mBrokenLibraries = false; - mIsResumedCalled = false; + mIsPaused = false; mIsSurfaceReady = false; mHasFocus = true; - mNextNativeState = NativeState.INIT; - mCurrentNativeState = NativeState.INIT; } // Setup @@ -145,9 +120,13 @@ public class SDLActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { Log.v(TAG, "Device: " + android.os.Build.DEVICE); Log.v(TAG, "Model: " + android.os.Build.MODEL); - Log.v(TAG, "onCreate()"); + Log.v(TAG, "onCreate(): " + mSingleton); super.onCreate(savedInstanceState); + SDLActivity.initialize(); + // So we can call stuff from static callbacks + mSingleton = this; + // Load shared libraries String errorMsgBrokenLib = ""; try { @@ -184,26 +163,16 @@ public class SDLActivity extends Activity { return; } - // Set up JNI - SDL.setupJNI(); - - // Initialize state - SDL.initialize(); - - // So we can call stuff from static callbacks - mSingleton = this; - SDL.setContext(this); - - if (Build.VERSION.SDK_INT >= 11) { - mClipboardHandler = new SDLClipboardHandler_API11(); - } else { - /* Before API 11, no clipboard notification (eg no SDL_CLIPBOARDUPDATE) */ - mClipboardHandler = new SDLClipboardHandler_Old(); - } - // Set up the surface mSurface = new SDLSurface(getApplication()); + if(Build.VERSION.SDK_INT >= 12) { + mJoystickHandler = new SDLJoystickHandler_API12(); + } + else { + mJoystickHandler = new SDLJoystickHandler(); + } + mLayout = new RelativeLayout(this); mLayout.addView(mSurface); @@ -211,6 +180,7 @@ public class SDLActivity extends Activity { // Get filename from "Open with" of another application Intent intent = getIntent(); + if (intent != null && intent.getData() != null) { String filename = intent.getData().getPath(); if (filename != null) { @@ -225,28 +195,24 @@ public class SDLActivity extends Activity { protected void onPause() { Log.v(TAG, "onPause()"); super.onPause(); - mNextNativeState = NativeState.PAUSED; - mIsResumedCalled = false; if (SDLActivity.mBrokenLibraries) { return; } - SDLActivity.handleNativeState(); + SDLActivity.handlePause(); } @Override protected void onResume() { Log.v(TAG, "onResume()"); super.onResume(); - mNextNativeState = NativeState.RESUMED; - mIsResumedCalled = true; if (SDLActivity.mBrokenLibraries) { return; } - SDLActivity.handleNativeState(); + SDLActivity.handleResume(); } @@ -261,12 +227,8 @@ public class SDLActivity extends Activity { SDLActivity.mHasFocus = hasFocus; if (hasFocus) { - mNextNativeState = NativeState.RESUMED; - } else { - mNextNativeState = NativeState.PAUSED; + SDLActivity.handleResume(); } - - SDLActivity.handleNativeState(); } @Override @@ -292,9 +254,6 @@ public class SDLActivity extends Activity { return; } - mNextNativeState = NativeState.PAUSED; - SDLActivity.handleNativeState(); - // Send a quit message to the application SDLActivity.mExitCalledFromJava = true; SDLActivity.nativeQuit(); @@ -312,7 +271,6 @@ public class SDLActivity extends Activity { } super.onDestroy(); - // Reset everything in case the user re opens the app SDLActivity.initialize(); } @@ -329,73 +287,35 @@ public class SDLActivity extends Activity { if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_CAMERA || - keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */ - keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */ + keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */ + keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */ ) { return false; } return super.dispatchKeyEvent(event); } - /* 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(); + /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed + * is the first to be called, mIsSurfaceReady should still be set + * to 'true' during the call to onPause (in a usual scenario). + */ + public static void handlePause() { + if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) { + SDLActivity.mIsPaused = true; + SDLActivity.nativePause(); mSurface.handlePause(); - mCurrentNativeState = mNextNativeState; - return; } + } - // Try a transition to resumed state - if (mNextNativeState == NativeState.RESUMED) { - if (mIsSurfaceReady && mHasFocus && mIsResumedCalled) { - if (mSDLThread == null) { - // This is the entry point to the C app. - // Start up the C app thread and enable sensor input for the first time - // FIXME: Why aren't we enabling sensor input at start? - - final Thread sdlThread = new Thread(new SDLMain(), "SDLThread"); - mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); - sdlThread.start(); - - // Set up a listener thread to catch when the native thread ends - mSDLThread = new Thread(new Runnable() { - @Override - public void run() { - try { - sdlThread.join(); - } catch (Exception e) { - // Ignore any exception - } finally { - // Native thread has finished - if (!mExitCalledFromJava) { - handleNativeExit(); - } - } - } - }, "SDLThreadListener"); - - mSDLThread.start(); - } - - nativeResume(); - mSurface.handleResume(); - mCurrentNativeState = mNextNativeState; - } + /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready. + * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume + * every time we get one of those events, only if it comes after surfaceDestroyed + */ + public static void handleResume() { + if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) { + SDLActivity.mIsPaused = false; + SDLActivity.nativeResume(); + mSurface.handleResume(); } } @@ -434,7 +354,7 @@ public class SDLActivity extends Activity { protected static class SDLCommandHandler extends Handler { @Override public void handleMessage(Message msg) { - Context context = SDL.getContext(); + Context context = getContext(); if (context == null) { Log.e(TAG, "error handling message, getContext() returned null"); return; @@ -456,20 +376,16 @@ public class SDLActivity extends Activity { InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); - - mScreenKeyboardShown = false; } break; case COMMAND_SET_KEEP_SCREEN_ON: { - if (context instanceof Activity) { - Window window = ((Activity) context).getWindow(); - if (window != null) { - if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } + Window window = ((Activity) context).getWindow(); + if (window != null) { + if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } break; @@ -494,14 +410,19 @@ public class SDLActivity extends Activity { } // C functions we call - public static native int nativeSetupJNI(); - public static native int nativeRunMain(String library, String function, Object arguments); + public static native int nativeInit(Object arguments); public static native void nativeLowMemory(); public static native void nativeQuit(); public static native void nativePause(); public static native void nativeResume(); public static native void onNativeDropFile(String filename); public static native void onNativeResize(int x, int y, int format, float rate); + 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); public static native void onNativeKeyDown(int keycode); public static native void onNativeKeyUp(int keycode); public static native void onNativeKeyboardFocusLost(); @@ -510,9 +431,12 @@ public class SDLActivity extends Activity { 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 onNativeSurfaceChanged(); public static native void onNativeSurfaceDestroyed(); + public static native int nativeAddJoystick(int device_id, String name, + int is_accelerometer, int nbuttons, + int naxes, int nhats, int nballs); + public static native int nativeRemoveJoystick(int device_id); public static native String nativeGetHint(String name); /** @@ -523,86 +447,10 @@ public class SDLActivity extends Activity { return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); } - /** - * 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) - { - if (mSingleton != null) { - mSingleton.setOrientationBis(w, h, resizable, hint); - } - } - - /** - * This can be overridden - */ - public void setOrientationBis(int w, int h, boolean resizable, String hint) - { - int orientation = -1; - - if (!Objects.equals(hint, "")) { - if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) { - orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; - } else if (hint.contains("LandscapeRight")) { - orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; - } else if (hint.contains("LandscapeLeft")) { - orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; - } else if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) { - orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; - } else if (hint.contains("Portrait")) { - orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; - } else if (hint.contains("PortraitUpsideDown")) { - orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; - } - } - - /* no valid hint */ - if (orientation == -1) { - if (resizable) { - /* no fixed orientation */ - } else { - if (w > h) { - orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; - } else { - orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; - } - } - } - - Log.v("SDL", "setOrientation() orientation=" + orientation + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); - if (orientation != -1) { - mSingleton.setRequestedOrientation(orientation); - } - } - - - /** - * This method is called by SDL using JNI. - */ - public static boolean isScreenKeyboardShown() - { - if (mTextEdit == null) { - return false; - } - - if (!mScreenKeyboardShown) { - return false; - } - - InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - return imm.isAcceptingText(); - - } - /** * This method is called by SDL using JNI. */ public static boolean sendMessage(int command, int param) { - if (mSingleton == null) { - return false; - } return mSingleton.sendCommand(command, Integer.valueOf(param)); } @@ -610,7 +458,36 @@ public class SDLActivity extends Activity { * This method is called by SDL using JNI. */ public static Context getContext() { - return SDL.getContext(); + return mSingleton; + } + + /** + * This method is called by SDL using JNI. + * @return result of getSystemService(name) but executed on UI thread. + */ + public Object getSystemServiceFromUiThread(final String name) { + final Object lock = new Object(); + final Object[] results = new Object[2]; // array for writable variables + synchronized (lock) { + runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (lock) { + results[0] = getSystemService(name); + results[1] = Boolean.TRUE; + lock.notify(); + } + } + }); + if (results[1] == null) { + try { + lock.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + } + return results[0]; } static class ShowTextInputTask implements Runnable { @@ -637,7 +514,7 @@ public class SDLActivity extends Activity { params.topMargin = y; if (mTextEdit == null) { - mTextEdit = new DummyEdit(SDL.getContext()); + mTextEdit = new DummyEdit(getContext()); mLayout.addView(mTextEdit, params); } else { @@ -647,10 +524,8 @@ public class SDLActivity extends Activity { mTextEdit.setVisibility(View.VISIBLE); mTextEdit.requestFocus(); - InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(mTextEdit, 0); - - mScreenKeyboardShown = true; } } @@ -662,28 +537,163 @@ public class SDLActivity extends Activity { return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); } - public static boolean isTextInputEvent(KeyEvent event) { - - // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT - if (android.os.Build.VERSION.SDK_INT >= 11) { - if (event.isCtrlPressed()) { - return false; - } + /** + * This method is called by SDL using JNI. + */ + public static Surface getNativeSurface() { + return SDLActivity.mSurface.getNativeSurface(); + } + + // Audio + + /** + * This method is called by SDL using JNI. + */ + public static int audioOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { + int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; + int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; + int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); + + Log.v(TAG, "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + // 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 + desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); + + 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) { + Log.e(TAG, "Failed during initialization of Audio Track"); + mAudioTrack = null; + return -1; + } + + mAudioTrack.play(); } - return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE; + Log.v(TAG, "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + return 0; } /** * This method is called by SDL using JNI. */ - public static Surface getNativeSurface() { - if (SDLActivity.mSurface == null) { - return null; + public static void audioWriteShortBuffer(short[] buffer) { + 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; + } } - return SDLActivity.mSurface.getNativeSurface(); } + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteByteBuffer(byte[] buffer) { + 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, boolean is16Bit, boolean isStereo, int desiredFrames) { + int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; + int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; + int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); + + Log.v(TAG, "SDL capture: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + // 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 + desiredFrames = Math.max(desiredFrames, (AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); + + 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 -1; + } + + mAudioRecord.startRecording(); + } + + Log.v(TAG, "SDL capture: got " + ((mAudioRecord.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioRecord.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioRecord.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + return 0; + } + + /** This method is called by SDL using JNI. */ + public static int captureReadShortBuffer(short[] buffer, boolean blocking) { + // !!! FIXME: this is available in API Level 23. Until then, we always block. :( + //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); + return mAudioRecord.read(buffer, 0, buffer.length); + } + + /** This method is called by SDL using JNI. */ + public static int captureReadByteBuffer(byte[] buffer, boolean blocking) { + // !!! FIXME: this is available in API Level 23. Until then, we always block. :( + //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); + return mAudioRecord.read(buffer, 0, buffer.length); + } + + + /** 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; + } + } + + // Input /** @@ -703,20 +713,49 @@ public class SDLActivity extends Activity { return Arrays.copyOf(filtered, used); } + // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance + public static boolean handleJoystickMotionEvent(MotionEvent event) { + return mJoystickHandler.handleMotionEvent(event); + } + + /** + * This method is called by SDL using JNI. + */ + public static void pollInputDevices() { + if (SDLActivity.mSDLThread != null) { + mJoystickHandler.pollInputDevices(); + } + } + + // Check if a given device is considered a possible SDL joystick + public static boolean isDeviceSDLJoystick(int deviceId) { + InputDevice device = InputDevice.getDevice(deviceId); + // We cannot use InputDevice.isVirtual before API 16, so let's accept + // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1) + if ((device == null) || (deviceId < 0)) { + return false; + } + int sources = device.getSources(); + return (((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) || + ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) || + ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) + ); + } + // APK expansion files support /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */ - private static Object expansionFile; + private Object expansionFile; /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */ - private static Method expansionFileMethod; + private Method expansionFileMethod; /** * This method is called by SDL using JNI. * @return an InputStream on success or null if no expansion file was used. * @throws IOException on errors. Message is set for the SDL error message. */ - public static InputStream openAPKExpansionInputStream(String fileName) throws IOException { + public InputStream openAPKExpansionInputStream(String fileName) throws IOException { // Get a ZipResourceFile representing a merger of both the main and patch files if (expansionFile == null) { String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION"); @@ -743,7 +782,7 @@ public class SDLActivity extends Activity { // not a part of Android SDK we access it using reflection expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport") .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class) - .invoke(null, SDL.getContext(), mainVersion, patchVersion); + .invoke(null, this, mainVersion, patchVersion); expansionFileMethod = expansionFile.getClass() .getMethod("getInputStream", String.class); @@ -922,7 +961,7 @@ public class SDLActivity extends Activity { mapping.put(KeyEvent.KEYCODE_ENTER, button); } if ((buttonFlags[i] & 0x00000002) != 0) { - mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */ + mapping.put(111, button); /* API 11: KeyEvent.KEYCODE_ESCAPE */ } } button.setText(buttonTexts[i]); @@ -977,45 +1016,18 @@ public class SDLActivity extends Activity { return dialog; } - - /** - * This method is called by SDL using JNI. - */ - public static boolean clipboardHasText() { - return mClipboardHandler.clipboardHasText(); - } - - /** - * This method is called by SDL using JNI. - */ - public static String clipboardGetText() { - return mClipboardHandler.clipboardGetText(); - } - - /** - * This method is called by SDL using JNI. - */ - public static void clipboardSetText(String string) { - mClipboardHandler.clipboardSetText(string); - } - } /** - Simple runnable to start the SDL application + Simple nativeInit() runnable */ class SDLMain implements Runnable { @Override public void run() { // Runs SDL_main() - String library = SDLActivity.mSingleton.getMainSharedObject(); - String function = SDLActivity.mSingleton.getMainFunction(); - String[] arguments = SDLActivity.mSingleton.getArguments(); + SDLActivity.nativeInit(SDLActivity.mSingleton.getArguments()); - Log.v("SDL", "Running main function " + function + " from library " + library); - SDLActivity.nativeRunMain(library, function, arguments); - - Log.v("SDL", "Finished main function"); + //Log.v("SDL", "SDL thread terminated"); } } @@ -1050,7 +1062,7 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); - if (Build.VERSION.SDK_INT >= 12) { + if(Build.VERSION.SDK_INT >= 12) { setOnGenericMotionListener(new SDLGenericMotionListener_API12()); } @@ -1087,11 +1099,8 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.v("SDL", "surfaceDestroyed()"); - - // Transition to pause, if needed - SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED; - SDLActivity.handleNativeState(); - + // Call this *before* setting mIsSurfaceReady to 'false' + SDLActivity.handlePause(); SDLActivity.mIsSurfaceReady = false; SDLActivity.onNativeSurfaceDestroyed(); } @@ -1160,12 +1169,12 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, { // Accept any } - else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) + else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { if (mWidth > mHeight) { skip = true; } - } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { + } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { if (mWidth < mHeight) { skip = true; } @@ -1184,17 +1193,45 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, if (skip) { Log.v("SDL", "Skip .. Surface is not ready."); - SDLActivity.mIsSurfaceReady = false; return; } - - /* Surface is ready */ - SDLActivity.mIsSurfaceReady = true; - /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */ + + // Set mIsSurfaceReady to 'true' *before* making a call to handleResume + SDLActivity.mIsSurfaceReady = true; SDLActivity.onNativeSurfaceChanged(); - SDLActivity.handleNativeState(); + + if (SDLActivity.mSDLThread == null) { + // This is the entry point to the C app. + // Start up the C app thread and enable sensor input for the first time + + final Thread sdlThread = new Thread(new SDLMain(), "SDLThread"); + enableSensor(Sensor.TYPE_ACCELEROMETER, true); + sdlThread.start(); + + // Set up a listener thread to catch when the native thread ends + SDLActivity.mSDLThread = new Thread(new Runnable(){ + @Override + public void run(){ + try { + sdlThread.join(); + } + catch(Exception e){} + finally{ + // Native thread has finished + if (! SDLActivity.mExitCalledFromJava) { + SDLActivity.handleNativeExit(); + } + } + } + }, "SDLThreadListener"); + SDLActivity.mSDLThread.start(); + } + + if (SDLActivity.mHasFocus) { + SDLActivity.handleResume(); + } } // Key events @@ -1207,14 +1244,14 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and // SOURCE_JOYSTICK, while its key events arrive from the keyboard source // So, retrieve the device itself and check all of its sources - if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) { + if (SDLActivity.isDeviceSDLJoystick(event.getDeviceId())) { // Note that we process events with specific key codes here if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (SDLControllerManager.onNativePadDown(event.getDeviceId(), keyCode) == 0) { + if (SDLActivity.onNativePadDown(event.getDeviceId(), keyCode) == 0) { return true; } } else if (event.getAction() == KeyEvent.ACTION_UP) { - if (SDLControllerManager.onNativePadUp(event.getDeviceId(), keyCode) == 0) { + if (SDLActivity.onNativePadUp(event.getDeviceId(), keyCode) == 0) { return true; } } @@ -1404,19 +1441,23 @@ class DummyEdit extends View implements View.OnKeyListener { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - /* - * This handles the hardware keyboard input - */ - if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (SDLActivity.isTextInputEvent(event)) { + + // This handles the hardware keyboard input + if (event.isPrintingKey() || keyCode == KeyEvent.KEYCODE_SPACE) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); } + return true; + } + + if (event.getAction() == KeyEvent.ACTION_DOWN) { SDLActivity.onNativeKeyDown(keyCode); return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { SDLActivity.onNativeKeyUp(keyCode); return true; } + return false; } @@ -1443,7 +1484,7 @@ class DummyEdit extends View implements View.OnKeyListener { outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI - | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */; + | 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */; return ic; } @@ -1458,17 +1499,20 @@ class SDLInputConnection extends BaseInputConnection { @Override public boolean sendKeyEvent(KeyEvent event) { + /* - * This handles the keycodes from soft keyboard (and IME-translated input from hardkeyboard) + * This handles the keycodes from soft keyboard (and IME-translated + * input from hardkeyboard) */ int keyCode = event.getKeyCode(); if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (SDLActivity.isTextInputEvent(event)) { + if (event.isPrintingKey() || keyCode == KeyEvent.KEYCODE_SPACE) { commitText(String.valueOf((char) event.getUnicodeChar()), 1); } SDLActivity.onNativeKeyDown(keyCode); return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { + SDLActivity.onNativeKeyUp(keyCode); return true; } @@ -1498,98 +1542,201 @@ class SDLInputConnection extends BaseInputConnection { @Override public boolean deleteSurroundingText(int beforeLength, int afterLength) { // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection - // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265 - if (beforeLength > 0 && afterLength == 0) { - boolean ret = true; - // backspace(s) - while (beforeLength-- > 0) { - boolean ret_key = sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) - && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); - ret = ret && ret_key; - } - return ret; + if (beforeLength == 1 && afterLength == 0) { + // backspace + return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) + && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); } return super.deleteSurroundingText(beforeLength, afterLength); } } -interface SDLClipboardHandler { +/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */ +class SDLJoystickHandler { - public boolean clipboardHasText(); - public String clipboardGetText(); - public void clipboardSetText(String string); + /** + * 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() { + } } +/* Actual joystick functionality available for API >= 12 devices */ +class SDLJoystickHandler_API12 extends SDLJoystickHandler { -class SDLClipboardHandler_API11 implements - SDLClipboardHandler, - android.content.ClipboardManager.OnPrimaryClipChangedListener { + static class SDLJoystick { + public int device_id; + public String name; + public ArrayList axes; + public ArrayList hats; + } + static class RangeComparator implements Comparator { + @Override + public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) { + return arg0.getAxis() - arg1.getAxis(); + } + } - protected android.content.ClipboardManager mClipMgr; + private ArrayList mJoysticks; - SDLClipboardHandler_API11() { - mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE); - mClipMgr.addPrimaryClipChangedListener(this); + public SDLJoystickHandler_API12() { + + mJoysticks = new ArrayList(); } @Override - public boolean clipboardHasText() { - return mClipMgr.hasText(); + public void pollInputDevices() { + int[] deviceIds = InputDevice.getDeviceIds(); + // It helps processing the device ids in reverse order + // For example, in the case of the XBox 360 wireless dongle, + // so the first controller seen by SDL matches what the receiver + // considers to be the first controller + + for(int i=deviceIds.length-1; i>-1; i--) { + SDLJoystick joystick = getJoystick(deviceIds[i]); + if (joystick == null) { + joystick = new SDLJoystick(); + InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]); + if (SDLActivity.isDeviceSDLJoystick(deviceIds[i])) { + joystick.device_id = deviceIds[i]; + joystick.name = joystickDevice.getName(); + joystick.axes = new ArrayList(); + joystick.hats = new ArrayList(); + + List ranges = joystickDevice.getMotionRanges(); + Collections.sort(ranges, new RangeComparator()); + for (InputDevice.MotionRange range : ranges ) { + if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + if (range.getAxis() == MotionEvent.AXIS_HAT_X || + range.getAxis() == MotionEvent.AXIS_HAT_Y) { + joystick.hats.add(range); + } + else { + joystick.axes.add(range); + } + } + } + + mJoysticks.add(joystick); + SDLActivity.nativeAddJoystick(joystick.device_id, joystick.name, 0, -1, + joystick.axes.size(), joystick.hats.size()/2, 0); + } + } + } + + /* Check removed devices */ + ArrayList removedDevices = new ArrayList(); + for(int i=0; i < mJoysticks.size(); i++) { + int device_id = mJoysticks.get(i).device_id; + int j; + for (j=0; j < deviceIds.length; j++) { + if (device_id == deviceIds[j]) break; + } + if (j == deviceIds.length) { + removedDevices.add(Integer.valueOf(device_id)); + } + } + + for(int i=0; i < removedDevices.size(); i++) { + int device_id = removedDevices.get(i).intValue(); + SDLActivity.nativeRemoveJoystick(device_id); + for (int j=0; j < mJoysticks.size(); j++) { + if (mJoysticks.get(j).device_id == device_id) { + mJoysticks.remove(j); + break; + } + } + } } - @Override - public String clipboardGetText() { - CharSequence text; - text = mClipMgr.getText(); - if (text != null) { - return text.toString(); + protected SDLJoystick getJoystick(int device_id) { + for(int i=0; i < mJoysticks.size(); i++) { + if (mJoysticks.get(i).device_id == device_id) { + return mJoysticks.get(i); + } } return null; } @Override - public void clipboardSetText(String string) { - mClipMgr.removePrimaryClipChangedListener(this); - mClipMgr.setText(string); - mClipMgr.addPrimaryClipChangedListener(this); - } - - @Override - public void onPrimaryClipChanged() { - SDLActivity.onNativeClipboardChanged(); - } - -} - -class SDLClipboardHandler_Old implements - SDLClipboardHandler { - - protected android.text.ClipboardManager mClipMgrOld; - - SDLClipboardHandler_Old() { - mClipMgrOld = (android.text.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE); - } - - @Override - public boolean clipboardHasText() { - return mClipMgrOld.hasText(); - } - - @Override - public String clipboardGetText() { - CharSequence text; - text = mClipMgrOld.getText(); - if (text != null) { - return text.toString(); - } - return null; - } - - @Override - public void clipboardSetText(String string) { - mClipMgrOld.setText(string); + public boolean handleMotionEvent(MotionEvent event) { + if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { + int actionPointerIndex = event.getActionIndex(); + int action = event.getActionMasked(); + switch(action) { + case MotionEvent.ACTION_MOVE: + SDLJoystick joystick = getJoystick(event.getDeviceId()); + if ( joystick != null ) { + for (int i = 0; i < joystick.axes.size(); i++) { + InputDevice.MotionRange range = joystick.axes.get(i); + /* Normalize the value to -1...1 */ + float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f; + SDLActivity.onNativeJoy(joystick.device_id, i, value ); + } + for (int i = 0; i < joystick.hats.size(); i+=2) { + int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) ); + int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) ); + SDLActivity.onNativeHat(joystick.device_id, i/2, hatX, hatY ); + } + } + break; + default: + break; + } + } + return true; } } +class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener { + // Generic Motion (mouse hover, joystick...) events go here + @Override + public boolean onGenericMotion(View v, MotionEvent event) { + float x, y; + int action; + + switch ( event.getSource() ) { + case InputDevice.SOURCE_JOYSTICK: + case InputDevice.SOURCE_GAMEPAD: + case InputDevice.SOURCE_DPAD: + return SDLActivity.handleJoystickMotionEvent(event); + + case InputDevice.SOURCE_MOUSE: + action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_SCROLL: + x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); + y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); + SDLActivity.onNativeMouse(0, action, x, y); + return true; + + case MotionEvent.ACTION_HOVER_MOVE: + x = event.getX(0); + y = event.getY(0); + + SDLActivity.onNativeMouse(0, action, x, y); + return true; + + default: + break; + } + break; + + default: + break; + } + + // Event was not managed + return false; + } +} diff --git a/build/android/src/org/libsdl/app/SDLAudioManager.java b/build/android/src/org/libsdl/app/SDLAudioManager.java deleted file mode 100644 index 26baf82..0000000 --- a/build/android/src/org/libsdl/app/SDLAudioManager.java +++ /dev/null @@ -1,178 +0,0 @@ -package org.libsdl.app; - -import android.media.*; -import android.util.Log; - -public class SDLAudioManager -{ - protected static final String TAG = "SDLAudio"; - - protected static AudioTrack mAudioTrack; - protected static AudioRecord mAudioRecord; - - public static void initialize() { - mAudioTrack = null; - mAudioRecord = null; - } - - // Audio - - /** - * This method is called by SDL using JNI. - */ - public static int audioOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { - int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; - int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; - int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); - - Log.v(TAG, "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); - - // 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 - desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); - - 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) { - Log.e(TAG, "Failed during initialization of Audio Track"); - mAudioTrack = null; - return -1; - } - - mAudioTrack.play(); - } - - Log.v(TAG, "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); - - return 0; - } - - /** - * 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, boolean is16Bit, boolean isStereo, int desiredFrames) { - int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; - int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; - int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); - - Log.v(TAG, "SDL capture: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); - - // 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 - desiredFrames = Math.max(desiredFrames, (AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); - - 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 -1; - } - - mAudioRecord.startRecording(); - } - - Log.v(TAG, "SDL capture: got " + ((mAudioRecord.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioRecord.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioRecord.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); - - return 0; - } - - /** This method is called by SDL using JNI. */ - public static int captureReadShortBuffer(short[] buffer, boolean blocking) { - // !!! FIXME: this is available in API Level 23. Until then, we always block. :( - //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); - return mAudioRecord.read(buffer, 0, buffer.length); - } - - /** This method is called by SDL using JNI. */ - public static int captureReadByteBuffer(byte[] buffer, boolean blocking) { - // !!! FIXME: this is available in API Level 23. Until then, we always block. :( - //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); - return mAudioRecord.read(buffer, 0, buffer.length); - } - - - /** 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; - } - } - - public static native int nativeSetupJNI(); -} diff --git a/build/android/src/org/libsdl/app/SDLControllerManager.java b/build/android/src/org/libsdl/app/SDLControllerManager.java deleted file mode 100644 index 3629442..0000000 --- a/build/android/src/org/libsdl/app/SDLControllerManager.java +++ /dev/null @@ -1,433 +0,0 @@ -package org.libsdl.app; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; - -import android.content.Context; -import android.os.*; -import android.view.*; -import android.util.Log; - - -public class SDLControllerManager -{ - - public static native int nativeSetupJNI(); - - public static native int nativeAddJoystick(int device_id, String name, String desc, - int is_accelerometer, int nbuttons, - 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); - - protected static SDLJoystickHandler mJoystickHandler; - protected static SDLHapticHandler mHapticHandler; - - private static final String TAG = "SDLControllerManager"; - - public static void initialize() { - mJoystickHandler = null; - mHapticHandler = null; - - SDLControllerManager.setup(); - } - - public static void setup() { - if (Build.VERSION.SDK_INT >= 16) { - mJoystickHandler = new SDLJoystickHandler_API16(); - } else if (Build.VERSION.SDK_INT >= 12) { - mJoystickHandler = new SDLJoystickHandler_API12(); - } else { - mJoystickHandler = new SDLJoystickHandler(); - } - mHapticHandler = new SDLHapticHandler(); - } - - // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance - public static boolean handleJoystickMotionEvent(MotionEvent event) { - return mJoystickHandler.handleMotionEvent(event); - } - - /** - * This method is called by SDL using JNI. - */ - public static void pollInputDevices() { - mJoystickHandler.pollInputDevices(); - } - - /** - * This method is called by SDL using JNI. - */ - public static void pollHapticDevices() { - mHapticHandler.pollHapticDevices(); - } - - /** - * This method is called by SDL using JNI. - */ - public static void hapticRun(int device_id, int length) { - mHapticHandler.run(device_id, length); - } - - // Check if a given device is considered a possible SDL joystick - public static boolean isDeviceSDLJoystick(int deviceId) { - InputDevice device = InputDevice.getDevice(deviceId); - // We cannot use InputDevice.isVirtual before API 16, so let's accept - // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1) - if ((device == null) || (deviceId < 0)) { - return false; - } - int sources = device.getSources(); - - if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) { - Log.v(TAG, "Input device " + device.getName() + " is a joystick."); - } - if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) { - Log.v(TAG, "Input device " + device.getName() + " is a dpad."); - } - if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { - Log.v(TAG, "Input device " + device.getName() + " is a gamepad."); - } - - return (((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) || - ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) || - ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) - ); - } - -} - -/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */ -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() { - } -} - -/* Actual joystick functionality available for API >= 12 devices */ -class SDLJoystickHandler_API12 extends SDLJoystickHandler { - - static class SDLJoystick { - public int device_id; - public String name; - public String desc; - public ArrayList axes; - public ArrayList hats; - } - static class RangeComparator implements Comparator { - @Override - public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) { - return arg0.getAxis() - arg1.getAxis(); - } - } - - private ArrayList mJoysticks; - - public SDLJoystickHandler_API12() { - - mJoysticks = new ArrayList(); - } - - @Override - public void pollInputDevices() { - int[] deviceIds = InputDevice.getDeviceIds(); - // It helps processing the device ids in reverse order - // For example, in the case of the XBox 360 wireless dongle, - // so the first controller seen by SDL matches what the receiver - // considers to be the first controller - - for(int i=deviceIds.length-1; i>-1; i--) { - SDLJoystick joystick = getJoystick(deviceIds[i]); - if (joystick == null) { - joystick = new SDLJoystick(); - InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]); - if (SDLControllerManager.isDeviceSDLJoystick(deviceIds[i])) { - joystick.device_id = deviceIds[i]; - joystick.name = joystickDevice.getName(); - joystick.desc = getJoystickDescriptor(joystickDevice); - joystick.axes = new ArrayList(); - joystick.hats = new ArrayList(); - - List ranges = joystickDevice.getMotionRanges(); - Collections.sort(ranges, new RangeComparator()); - for (InputDevice.MotionRange range : ranges ) { - if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { - if (range.getAxis() == MotionEvent.AXIS_HAT_X || - range.getAxis() == MotionEvent.AXIS_HAT_Y) { - joystick.hats.add(range); - } - else { - joystick.axes.add(range); - } - } - } - - mJoysticks.add(joystick); - SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, 0, -1, - joystick.axes.size(), joystick.hats.size()/2, 0); - } - } - } - - /* Check removed devices */ - ArrayList removedDevices = new ArrayList(); - for(int i=0; i < mJoysticks.size(); i++) { - int device_id = mJoysticks.get(i).device_id; - int j; - for (j=0; j < deviceIds.length; j++) { - if (device_id == deviceIds[j]) break; - } - if (j == deviceIds.length) { - removedDevices.add(Integer.valueOf(device_id)); - } - } - - for(int i=0; i < removedDevices.size(); i++) { - int device_id = removedDevices.get(i).intValue(); - SDLControllerManager.nativeRemoveJoystick(device_id); - for (int j=0; j < mJoysticks.size(); j++) { - if (mJoysticks.get(j).device_id == device_id) { - mJoysticks.remove(j); - break; - } - } - } - } - - protected SDLJoystick getJoystick(int device_id) { - for(int i=0; i < mJoysticks.size(); i++) { - if (mJoysticks.get(i).device_id == device_id) { - return mJoysticks.get(i); - } - } - return null; - } - - @Override - public boolean handleMotionEvent(MotionEvent event) { - if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { - int actionPointerIndex = event.getActionIndex(); - int action = event.getActionMasked(); - switch(action) { - case MotionEvent.ACTION_MOVE: - SDLJoystick joystick = getJoystick(event.getDeviceId()); - if ( joystick != null ) { - for (int i = 0; i < joystick.axes.size(); i++) { - InputDevice.MotionRange range = joystick.axes.get(i); - /* Normalize the value to -1...1 */ - float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f; - SDLControllerManager.onNativeJoy(joystick.device_id, i, value ); - } - for (int i = 0; i < joystick.hats.size(); i+=2) { - int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) ); - int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) ); - SDLControllerManager.onNativeHat(joystick.device_id, i/2, hatX, hatY ); - } - } - break; - default: - break; - } - } - return true; - } - - public String getJoystickDescriptor(InputDevice joystickDevice) { - return joystickDevice.getName(); - } -} - - -class SDLJoystickHandler_API16 extends SDLJoystickHandler_API12 { - - @Override - public String getJoystickDescriptor(InputDevice joystickDevice) { - String desc = joystickDevice.getDescriptor(); - - if (desc != null && !Objects.equals(desc, "")) { - return desc; - } - - return super.getJoystickDescriptor(joystickDevice); - } -} - -class SDLHapticHandler { - - class SDLHaptic { - public int device_id; - public String name; - public Vibrator vib; - } - - private ArrayList mHaptics; - - public SDLHapticHandler() { - mHaptics = new ArrayList(); - } - - public void run(int device_id, int length) { - SDLHaptic haptic = getHaptic(device_id); - if (haptic != null) { - haptic.vib.vibrate (length); - } - } - - public void pollHapticDevices() { - - final int deviceId_VIBRATOR_SERVICE = 999999; - boolean hasVibratorService = false; - - int[] deviceIds = InputDevice.getDeviceIds(); - // It helps processing the device ids in reverse order - // For example, in the case of the XBox 360 wireless dongle, - // so the first controller seen by SDL matches what the receiver - // considers to be the first controller - - if (Build.VERSION.SDK_INT >= 16) - { - for (int i = deviceIds.length - 1; i > -1; i--) { - SDLHaptic haptic = getHaptic(deviceIds[i]); - if (haptic == null) { - InputDevice device = InputDevice.getDevice(deviceIds[i]); - Vibrator vib = device.getVibrator(); - if (vib.hasVibrator()) { - haptic = new SDLHaptic(); - haptic.device_id = deviceIds[i]; - haptic.name = device.getName(); - haptic.vib = vib; - mHaptics.add(haptic); - SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); - } - } - } - } - - /* Check VIBRATOR_SERVICE */ - Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE); - if (vib != null) { - if (Build.VERSION.SDK_INT >= 11) { - hasVibratorService = vib.hasVibrator(); - } else { - hasVibratorService = true; - } - - if (hasVibratorService) { - SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE); - if (haptic == null) { - haptic = new SDLHaptic(); - haptic.device_id = deviceId_VIBRATOR_SERVICE; - haptic.name = "VIBRATOR_SERVICE"; - haptic.vib = vib; - mHaptics.add(haptic); - SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); - } - } - } - - /* Check removed devices */ - ArrayList removedDevices = new ArrayList(); - for(int i=0; i < mHaptics.size(); i++) { - int device_id = mHaptics.get(i).device_id; - int j; - for (j=0; j < deviceIds.length; j++) { - if (device_id == deviceIds[j]) break; - } - - if (device_id == deviceId_VIBRATOR_SERVICE && hasVibratorService) { - // don't remove the vibrator if it is still present - } else if (j == deviceIds.length) { - removedDevices.add(device_id); - } - } - - for(int i=0; i < removedDevices.size(); i++) { - int device_id = removedDevices.get(i); - SDLControllerManager.nativeRemoveHaptic(device_id); - for (int j=0; j < mHaptics.size(); j++) { - if (mHaptics.get(j).device_id == device_id) { - mHaptics.remove(j); - break; - } - } - } - } - - protected SDLHaptic getHaptic(int device_id) { - for(int i=0; i < mHaptics.size(); i++) { - if (mHaptics.get(i).device_id == device_id) { - return mHaptics.get(i); - } - } - return null; - } -} - -class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener { - // Generic Motion (mouse hover, joystick...) events go here - @Override - public boolean onGenericMotion(View v, MotionEvent event) { - float x, y; - int action; - - switch ( event.getSource() ) { - case InputDevice.SOURCE_JOYSTICK: - case InputDevice.SOURCE_GAMEPAD: - case InputDevice.SOURCE_DPAD: - return SDLControllerManager.handleJoystickMotionEvent(event); - - case InputDevice.SOURCE_MOUSE: - if (!SDLActivity.mSeparateMouseAndTouch) { - break; - } - action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_SCROLL: - x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); - y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); - SDLActivity.onNativeMouse(0, action, x, y); - return true; - - case MotionEvent.ACTION_HOVER_MOVE: - x = event.getX(0); - y = event.getY(0); - - SDLActivity.onNativeMouse(0, action, x, y); - return true; - - default: - break; - } - break; - - default: - break; - } - - // Event was not managed - return false; - } -} -