diff --git a/build/android/AndroidManifest.xml b/build/android/AndroidManifest.xml
index 9d16a29..745120d 100644
--- a/build/android/AndroidManifest.xml
+++ b/build/android/AndroidManifest.xml
@@ -8,8 +8,7 @@
android:versionName="0.60.3"
android:installLocation="auto">
-
-
+
@@ -34,7 +33,7 @@
android:hardwareAccelerated="true" >
diff --git a/build/android/ant.properties b/build/android/ant.properties
index 4169587..7d029c5 100644
--- a/build/android/ant.properties
+++ b/build/android/ant.properties
@@ -16,4 +16,6 @@
# The password will be asked during the build when you use the 'release' target.
key.store=my-release-key.keystore
-key.alias=alias_name
\ No newline at end of file
+key.alias=alias_name
+java.source=8
+java.target=8
diff --git a/build/android/default.properties b/build/android/default.properties
index 0cdab95..0a69b77 100644
--- a/build/android/default.properties
+++ b/build/android/default.properties
@@ -8,4 +8,4 @@
# project structure.
# Project target.
-target=android-12
+target=android-16
diff --git a/build/android/jni/Application.mk b/build/android/jni/Application.mk
index 5ea0fb4..7154e7c 100644
--- a/build/android/jni/Application.mk
+++ b/build/android/jni/Application.mk
@@ -6,5 +6,4 @@
APP_ABI := armeabi armeabi-v7a x86
# Min SDK level
-APP_PLATFORM=android-10
-
+APP_PLATFORM=android-14
diff --git a/build/android/jni/src/Android.mk b/build/android/jni/src/Android.mk
index 6dbed24..72d1977 100644
--- a/build/android/jni/src/Android.mk
+++ b/build/android/jni/src/Android.mk
@@ -8,7 +8,7 @@ SRC_PATH := ../../../../src
THIRD_PARTY_PATH := ../../../../../3rd-party
LOCAL_C_INCLUDES := \
- $(LOCAL_PATH)/$(THIRD_PARTY_PATH)/SDL2-2.0.5/include \
+ $(LOCAL_PATH)/$(THIRD_PARTY_PATH)/SDL2-2.0.7/include \
$(LOCAL_PATH)/$(THIRD_PARTY_PATH)/lua-5.3.1/src \
$(LOCAL_PATH)/$(THIRD_PARTY_PATH)/giflib-5.1.4/lib \
$(LOCAL_PATH)/$(THIRD_PARTY_PATH)/zlib-1.2.8 \
@@ -20,7 +20,6 @@ LOCAL_C_INCLUDES := \
# Add your application source files here...
LOCAL_SRC_FILES := \
- $(THIRD_PARTY_PATH)/SDL2-2.0.5/src/main/android/SDL_android_main.c \
$(THIRD_PARTY_PATH)/duktape-2.2.0/src/duktape.c \
$(THIRD_PARTY_PATH)/blip-buf/blip_buf.c \
$(THIRD_PARTY_PATH)/SDL2_net-2.0.1/SDLnet.c \
diff --git a/build/android/project.properties b/build/android/project.properties
index 0f507e5..9b84a6b 100644
--- a/build/android/project.properties
+++ b/build/android/project.properties
@@ -11,4 +11,4 @@
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
-target=android-12
+target=android-16
diff --git a/build/android/src/org/libsdl/app/SDL.java b/build/android/src/org/libsdl/app/SDL.java
new file mode 100644
index 0000000..cfe4830
--- /dev/null
+++ b/build/android/src/org/libsdl/app/SDL.java
@@ -0,0 +1,37 @@
+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 c3d4484..27c45ed 100644
--- a/build/android/src/org/libsdl/app/SDLActivity.java
+++ b/build/android/src/org/libsdl/app/SDLActivity.java
@@ -2,11 +2,7 @@ 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 android.app.*;
@@ -26,7 +22,6 @@ 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;
@@ -36,8 +31,16 @@ import android.content.pm.ActivityInfo;
public class SDLActivity extends Activity {
private static final String TAG = "SDL";
- // Keep track of the paused state
- public static boolean mIsPaused, mIsSurfaceReady, mHasFocus;
+ 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;
+
public static boolean mExitCalledFromJava;
/** If shared libraries (e.g. SDL or the native application) could not be loaded. */
@@ -51,15 +54,36 @@ 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 SDLJoystickHandler mJoystickHandler;
+ protected static SDLClipboardHandler mClipboardHandler;
+
// This is what SDL runs in. It invokes SDL_main(), eventually
protected static Thread mSDLThread;
- // Audio
- protected static AudioTrack mAudioTrack;
- protected static AudioRecord mAudioRecord;
+ /**
+ * 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";
+ }
/**
* This method is called by SDL before loading the native shared libraries.
@@ -104,15 +128,15 @@ public class SDLActivity extends Activity {
mSurface = null;
mTextEdit = null;
mLayout = null;
- mJoystickHandler = null;
+ mClipboardHandler = null;
mSDLThread = null;
- mAudioTrack = null;
- mAudioRecord = null;
mExitCalledFromJava = false;
mBrokenLibraries = false;
- mIsPaused = false;
+ mIsResumedCalled = false;
mIsSurfaceReady = false;
mHasFocus = true;
+ mNextNativeState = NativeState.INIT;
+ mCurrentNativeState = NativeState.INIT;
}
// Setup
@@ -120,13 +144,9 @@ 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(): " + mSingleton);
+ Log.v(TAG, "onCreate()");
super.onCreate(savedInstanceState);
- SDLActivity.initialize();
- // So we can call stuff from static callbacks
- mSingleton = this;
-
// Load shared libraries
String errorMsgBrokenLib = "";
try {
@@ -163,16 +183,26 @@ 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);
@@ -180,7 +210,6 @@ 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) {
@@ -195,24 +224,28 @@ public class SDLActivity extends Activity {
protected void onPause() {
Log.v(TAG, "onPause()");
super.onPause();
+ mNextNativeState = NativeState.PAUSED;
+ mIsResumedCalled = false;
if (SDLActivity.mBrokenLibraries) {
return;
}
- SDLActivity.handlePause();
+ SDLActivity.handleNativeState();
}
@Override
protected void onResume() {
Log.v(TAG, "onResume()");
super.onResume();
+ mNextNativeState = NativeState.RESUMED;
+ mIsResumedCalled = true;
if (SDLActivity.mBrokenLibraries) {
return;
}
- SDLActivity.handleResume();
+ SDLActivity.handleNativeState();
}
@@ -227,8 +260,12 @@ public class SDLActivity extends Activity {
SDLActivity.mHasFocus = hasFocus;
if (hasFocus) {
- SDLActivity.handleResume();
+ mNextNativeState = NativeState.RESUMED;
+ } else {
+ mNextNativeState = NativeState.PAUSED;
}
+
+ SDLActivity.handleNativeState();
}
@Override
@@ -254,6 +291,9 @@ public class SDLActivity extends Activity {
return;
}
+ mNextNativeState = NativeState.PAUSED;
+ SDLActivity.handleNativeState();
+
// Send a quit message to the application
SDLActivity.mExitCalledFromJava = true;
SDLActivity.nativeQuit();
@@ -271,6 +311,7 @@ public class SDLActivity extends Activity {
}
super.onDestroy();
+
// Reset everything in case the user re opens the app
SDLActivity.initialize();
}
@@ -287,35 +328,73 @@ public class SDLActivity extends Activity {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
keyCode == KeyEvent.KEYCODE_CAMERA ||
- keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */
- keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */
+ keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */
+ keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */
) {
return false;
}
return super.dispatchKeyEvent(event);
}
- /** 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();
- }
- }
+ /* Transition to next state */
+ public static void handleNativeState() {
- /** 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();
+ 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();
+ 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;
+ }
}
}
@@ -354,7 +433,7 @@ public class SDLActivity extends Activity {
protected static class SDLCommandHandler extends Handler {
@Override
public void handleMessage(Message msg) {
- Context context = getContext();
+ Context context = SDL.getContext();
if (context == null) {
Log.e(TAG, "error handling message, getContext() returned null");
return;
@@ -376,16 +455,20 @@ 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:
{
- 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);
+ 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);
+ }
}
}
break;
@@ -410,19 +493,14 @@ public class SDLActivity extends Activity {
}
// C functions we call
- public static native int nativeInit(Object arguments);
+ 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 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();
@@ -431,12 +509,9 @@ 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);
/**
@@ -447,10 +522,86 @@ 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 (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));
}
@@ -458,36 +609,7 @@ public class SDLActivity extends Activity {
* This method is called by SDL using JNI.
*/
public static Context 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];
+ return SDL.getContext();
}
static class ShowTextInputTask implements Runnable {
@@ -514,7 +636,7 @@ public class SDLActivity extends Activity {
params.topMargin = y;
if (mTextEdit == null) {
- mTextEdit = new DummyEdit(getContext());
+ mTextEdit = new DummyEdit(SDL.getContext());
mLayout.addView(mTextEdit, params);
} else {
@@ -524,8 +646,10 @@ public class SDLActivity extends Activity {
mTextEdit.setVisibility(View.VISIBLE);
mTextEdit.requestFocus();
- InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(mTextEdit, 0);
+
+ mScreenKeyboardShown = true;
}
}
@@ -537,163 +661,28 @@ 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;
+ }
+ }
+
+ return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;
+ }
+
/**
* This method is called by SDL using JNI.
*/
public static Surface getNativeSurface() {
+ if (SDLActivity.mSurface == null) {
+ return null;
+ }
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();
- }
-
- 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) {
- 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) {
- 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
/**
@@ -713,49 +702,20 @@ 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 Object expansionFile;
+ private static Object expansionFile;
/** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
- private Method expansionFileMethod;
+ private static 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 InputStream openAPKExpansionInputStream(String fileName) throws IOException {
+ public static 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");
@@ -782,7 +742,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, this, mainVersion, patchVersion);
+ .invoke(null, SDL.getContext(), mainVersion, patchVersion);
expansionFileMethod = expansionFile.getClass()
.getMethod("getInputStream", String.class);
@@ -961,7 +921,7 @@ public class SDLActivity extends Activity {
mapping.put(KeyEvent.KEYCODE_ENTER, button);
}
if ((buttonFlags[i] & 0x00000002) != 0) {
- mapping.put(111, button); /* API 11: KeyEvent.KEYCODE_ESCAPE */
+ mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */
}
}
button.setText(buttonTexts[i]);
@@ -1016,18 +976,45 @@ 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 nativeInit() runnable
+ Simple runnable to start the SDL application
*/
class SDLMain implements Runnable {
@Override
public void run() {
// Runs SDL_main()
- SDLActivity.nativeInit(SDLActivity.mSingleton.getArguments());
+ String library = SDLActivity.mSingleton.getMainSharedObject();
+ String function = SDLActivity.mSingleton.getMainFunction();
+ String[] arguments = SDLActivity.mSingleton.getArguments();
- //Log.v("SDL", "SDL thread terminated");
+ Log.v("SDL", "Running main function " + function + " from library " + library);
+ SDLActivity.nativeRunMain(library, function, arguments);
+
+ Log.v("SDL", "Finished main function");
}
}
@@ -1062,7 +1049,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());
}
@@ -1099,8 +1086,11 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.v("SDL", "surfaceDestroyed()");
- // Call this *before* setting mIsSurfaceReady to 'false'
- SDLActivity.handlePause();
+
+ // Transition to pause, if needed
+ SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
+ SDLActivity.handleNativeState();
+
SDLActivity.mIsSurfaceReady = false;
SDLActivity.onNativeSurfaceDestroyed();
}
@@ -1169,12 +1159,12 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
{
// Accept any
}
- else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT)
{
if (mWidth > mHeight) {
skip = true;
}
- } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
+ } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
if (mWidth < mHeight) {
skip = true;
}
@@ -1193,45 +1183,17 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
if (skip) {
Log.v("SDL", "Skip .. Surface is not ready.");
+ SDLActivity.mIsSurfaceReady = false;
return;
}
-
-
- // Set mIsSurfaceReady to 'true' *before* making a call to handleResume
+
+ /* Surface is ready */
SDLActivity.mIsSurfaceReady = true;
+
+ /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
SDLActivity.onNativeSurfaceChanged();
-
- 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();
- }
+ SDLActivity.handleNativeState();
}
// Key events
@@ -1244,14 +1206,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 (SDLActivity.isDeviceSDLJoystick(event.getDeviceId())) {
+ if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) {
// Note that we process events with specific key codes here
if (event.getAction() == KeyEvent.ACTION_DOWN) {
- if (SDLActivity.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
+ if (SDLControllerManager.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
return true;
}
} else if (event.getAction() == KeyEvent.ACTION_UP) {
- if (SDLActivity.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
+ if (SDLControllerManager.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
return true;
}
}
@@ -1441,23 +1403,19 @@ 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.isPrintingKey() || keyCode == KeyEvent.KEYCODE_SPACE) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ /*
+ * This handles the hardware keyboard input
+ */
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ if (SDLActivity.isTextInputEvent(event)) {
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;
}
@@ -1484,7 +1442,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
- | 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */;
+ | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
return ic;
}
@@ -1499,20 +1457,17 @@ 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 (event.isPrintingKey() || keyCode == KeyEvent.KEYCODE_SPACE) {
+ if (SDLActivity.isTextInputEvent(event)) {
commitText(String.valueOf((char) event.getUnicodeChar()), 1);
}
SDLActivity.onNativeKeyDown(keyCode);
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
-
SDLActivity.onNativeKeyUp(keyCode);
return true;
}
@@ -1542,201 +1497,98 @@ 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
- 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));
+ // 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;
}
return super.deleteSurroundingText(beforeLength, afterLength);
}
}
-/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */
-class SDLJoystickHandler {
+interface SDLClipboardHandler {
- /**
- * Handles given MotionEvent.
- * @param event the event to be handled.
- * @return if given event was processed.
- */
- public boolean handleMotionEvent(MotionEvent event) {
- return false;
- }
+ public boolean clipboardHasText();
+ public String clipboardGetText();
+ public void clipboardSetText(String string);
- /**
- * 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 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();
- }
- }
+class SDLClipboardHandler_API11 implements
+ SDLClipboardHandler,
+ android.content.ClipboardManager.OnPrimaryClipChangedListener {
- private ArrayList mJoysticks;
+ protected android.content.ClipboardManager mClipMgr;
- public SDLJoystickHandler_API12() {
-
- mJoysticks = new ArrayList();
+ SDLClipboardHandler_API11() {
+ mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
+ mClipMgr.addPrimaryClipChangedListener(this);
}
@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 (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;
- }
- }
- }
+ public boolean clipboardHasText() {
+ return mClipMgr.hasText();
}
- 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);
- }
+ @Override
+ public String clipboardGetText() {
+ CharSequence text;
+ text = mClipMgr.getText();
+ if (text != null) {
+ return text.toString();
}
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;
- 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;
+ public void clipboardSetText(String string) {
+ mClipMgr.removePrimaryClipChangedListener(this);
+ mClipMgr.setText(string);
+ mClipMgr.addPrimaryClipChangedListener(this);
}
-}
-
-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;
+ public void onPrimaryClipChanged() {
+ SDLActivity.onNativeClipboardChanged();
+ }
- 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;
+class SDLClipboardHandler_Old implements
+ SDLClipboardHandler {
+
+ protected android.text.ClipboardManager mClipMgrOld;
+
+ SDLClipboardHandler_Old() {
+ mClipMgrOld = (android.text.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
+ }
- case MotionEvent.ACTION_HOVER_MOVE:
- x = event.getX(0);
- y = event.getY(0);
+ @Override
+ public boolean clipboardHasText() {
+ return mClipMgrOld.hasText();
+ }
- SDLActivity.onNativeMouse(0, action, x, y);
- return true;
+ @Override
+ public String clipboardGetText() {
+ CharSequence text;
+ text = mClipMgrOld.getText();
+ if (text != null) {
+ return text.toString();
+ }
+ return null;
+ }
- default:
- break;
- }
- break;
-
- default:
- break;
- }
-
- // Event was not managed
- return false;
+ @Override
+ public void clipboardSetText(String string) {
+ mClipMgrOld.setText(string);
}
}
+
diff --git a/build/android/src/org/libsdl/app/SDLAudioManager.java b/build/android/src/org/libsdl/app/SDLAudioManager.java
new file mode 100644
index 0000000..26baf82
--- /dev/null
+++ b/build/android/src/org/libsdl/app/SDLAudioManager.java
@@ -0,0 +1,178 @@
+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
new file mode 100644
index 0000000..34729f6
--- /dev/null
+++ b/build/android/src/org/libsdl/app/SDLControllerManager.java
@@ -0,0 +1,432 @@
+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.*;
+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 && 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;
+ }
+}
+