2011-01-06 17:12:31 -08:00
package org.libsdl.app ;
2011-08-26 13:15:05 +01:00
import javax.microedition.khronos.egl.EGL10 ;
2011-01-06 17:12:31 -08:00
import javax.microedition.khronos.egl.EGLConfig ;
2011-08-26 13:15:05 +01:00
import javax.microedition.khronos.egl.EGLContext ;
2013-05-18 14:03:45 +02:00
import javax.microedition.khronos.egl.EGLDisplay ;
import javax.microedition.khronos.egl.EGLSurface ;
2011-01-06 17:12:31 -08:00
import android.app.* ;
import android.content.* ;
import android.view.* ;
2012-10-03 20:49:16 -07:00
import android.view.inputmethod.BaseInputConnection ;
import android.view.inputmethod.EditorInfo ;
import android.view.inputmethod.InputConnection ;
2012-08-11 10:15:59 -07:00
import android.view.inputmethod.InputMethodManager ;
2012-10-03 20:49:16 -07:00
import android.widget.AbsoluteLayout ;
2011-01-06 17:12:31 -08:00
import android.os.* ;
import android.util.Log ;
import android.graphics.* ;
import android.media.* ;
import android.hardware.* ;
/ * *
SDL Activity
* /
public class SDLActivity extends Activity {
2013-05-05 12:50:34 +02:00
private static final String TAG = " SDL " ;
2011-01-06 17:12:31 -08:00
2012-06-24 21:10:17 -03:00
// Keep track of the paused state
2013-07-09 10:25:16 -03:00
public static boolean mIsPaused = false , mIsSurfaceReady = false , mHasFocus = true ;
2012-06-24 21:10:17 -03:00
2011-01-12 14:53:23 -08:00
// Main components
2013-05-15 23:18:29 -07:00
protected static SDLActivity mSingleton ;
protected static SDLSurface mSurface ;
protected static View mTextEdit ;
protected static ViewGroup mLayout ;
2011-01-06 17:12:31 -08:00
2012-01-08 01:05:25 -05:00
// This is what SDL runs in. It invokes SDL_main(), eventually
2013-05-15 23:18:29 -07:00
protected static Thread mSDLThread ;
2012-01-08 01:05:25 -05:00
2011-01-12 14:53:23 -08:00
// Audio
2013-05-15 23:18:29 -07:00
protected static Thread mAudioThread ;
protected static AudioTrack mAudioTrack ;
2011-01-06 17:12:31 -08:00
2013-05-18 14:04:37 +02:00
// EGL objects
2013-05-15 23:18:29 -07:00
protected static EGLContext mEGLContext ;
protected static EGLSurface mEGLSurface ;
protected static EGLDisplay mEGLDisplay ;
protected static EGLConfig mEGLConfig ;
protected static int mGLMajor , mGLMinor ;
2012-01-08 01:05:25 -05:00
2011-01-12 14:53:23 -08:00
// Load the .so
2011-01-06 17:12:31 -08:00
static {
2012-02-04 16:39:52 -05:00
System . loadLibrary ( " SDL2 " ) ;
//System.loadLibrary("SDL2_image");
//System.loadLibrary("SDL2_mixer");
2013-05-30 12:23:36 +02:00
//System.loadLibrary("SDL2_net");
2012-02-04 16:39:52 -05:00
//System.loadLibrary("SDL2_ttf");
2011-01-06 17:12:31 -08:00
System . loadLibrary ( " main " ) ;
}
2011-01-12 14:53:23 -08:00
// Setup
2013-04-27 14:13:30 +02:00
@Override
2011-01-06 17:12:31 -08:00
protected void onCreate ( Bundle savedInstanceState ) {
2011-01-12 17:53:06 -08:00
//Log.v("SDL", "onCreate()");
2011-01-06 17:12:31 -08:00
super . onCreate ( savedInstanceState ) ;
2011-01-12 14:53:23 -08:00
// So we can call stuff from static callbacks
2011-01-06 17:12:31 -08:00
mSingleton = this ;
2011-01-12 14:53:23 -08:00
// Set up the surface
2013-07-06 15:22:49 -03:00
mEGLSurface = EGL10 . EGL_NO_SURFACE ;
2011-01-06 17:12:31 -08:00
mSurface = new SDLSurface ( getApplication ( ) ) ;
2012-10-03 20:49:16 -07:00
mLayout = new AbsoluteLayout ( this ) ;
mLayout . addView ( mSurface ) ;
setContentView ( mLayout ) ;
2011-01-06 17:12:31 -08:00
}
2011-01-12 14:29:01 -08:00
// Events
2013-05-18 12:48:50 -07:00
@Override
protected void onPause ( ) {
2012-01-08 01:05:25 -05:00
Log . v ( " SDL " , " onPause() " ) ;
2011-01-06 17:12:31 -08:00
super . onPause ( ) ;
2013-07-06 15:22:49 -03:00
SDLActivity . handlePause ( ) ;
2011-01-06 17:12:31 -08:00
}
2013-05-18 12:48:50 -07:00
@Override
2011-01-06 17:12:31 -08:00
protected void onResume ( ) {
2012-01-08 01:05:25 -05:00
Log . v ( " SDL " , " onResume() " ) ;
2011-01-06 17:12:31 -08:00
super . onResume ( ) ;
2013-07-06 15:22:49 -03:00
SDLActivity . handleResume ( ) ;
2013-05-18 12:48:50 -07:00
}
2013-07-09 10:25:16 -03:00
@Override
public void onWindowFocusChanged ( boolean hasFocus ) {
super . onWindowFocusChanged ( hasFocus ) ;
Log . v ( " SDL " , " onWindowFocusChanged(): " + hasFocus ) ;
SDLActivity . mHasFocus = hasFocus ;
if ( hasFocus ) {
SDLActivity . handleResume ( ) ;
}
}
2013-05-18 12:48:50 -07:00
@Override
public void onLowMemory ( ) {
Log . v ( " SDL " , " onLowMemory() " ) ;
super . onLowMemory ( ) ;
SDLActivity . nativeLowMemory ( ) ;
}
2012-01-08 01:05:25 -05:00
2013-04-27 14:13:30 +02:00
@Override
2012-01-08 01:05:25 -05:00
protected void onDestroy ( ) {
super . onDestroy ( ) ;
Log . v ( " SDL " , " onDestroy() " ) ;
// Send a quit message to the application
SDLActivity . nativeQuit ( ) ;
// Now wait for the SDL thread to quit
if ( mSDLThread ! = null ) {
try {
mSDLThread . join ( ) ;
} catch ( Exception e ) {
Log . v ( " SDL " , " Problem stopping thread: " + e ) ;
}
mSDLThread = null ;
//Log.v("SDL", "Finished waiting for SDL thread");
}
2011-01-06 17:12:31 -08:00
}
2013-07-06 15:22:49 -03:00
/ * * 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 . enableSensor ( Sensor . TYPE_ACCELEROMETER , false ) ;
}
}
/ * * 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 ( ) {
2013-07-09 10:25:16 -03:00
if ( SDLActivity . mIsPaused & & SDLActivity . mIsSurfaceReady & & SDLActivity . mHasFocus ) {
2013-07-06 15:22:49 -03:00
SDLActivity . mIsPaused = false ;
SDLActivity . nativeResume ( ) ;
mSurface . enableSensor ( Sensor . TYPE_ACCELEROMETER , true ) ;
}
}
2011-01-13 15:10:17 -08:00
// Messages from the SDLMain thread
2012-08-11 10:15:59 -07:00
static final int COMMAND_CHANGE_TITLE = 1 ;
2012-11-04 21:53:28 -08:00
static final int COMMAND_UNUSED = 2 ;
2012-10-03 20:49:16 -07:00
static final int COMMAND_TEXTEDIT_HIDE = 3 ;
2011-01-13 15:10:17 -08:00
2013-05-05 12:53:57 +02:00
protected static final int COMMAND_USER = 0x8000 ;
/ * *
* This method is called by SDL if SDL did not handle a message itself .
* This happens if a received message contains an unsupported command .
* Method can be overwritten to handle Messages in a different class .
2013-05-05 15:50:21 +02:00
* @param command the command of the message .
* @param param the parameter of the message . May be null .
* @return if the message was handled in overridden method .
2013-05-05 12:53:57 +02:00
* /
2013-05-05 15:50:21 +02:00
protected boolean onUnhandledMessage ( int command , Object param ) {
2013-05-05 12:53:57 +02:00
return false ;
}
2013-05-05 12:50:34 +02:00
/ * *
* A Handler class for Messages from native SDL applications .
* It uses current Activities as target ( e . g . for the title ) .
* static to prevent implicit references to enclosing object .
* /
protected static class SDLCommandHandler extends Handler {
2012-08-11 10:15:59 -07:00
@Override
2011-01-13 15:10:17 -08:00
public void handleMessage ( Message msg ) {
2013-05-05 12:50:34 +02:00
Context context = getContext ( ) ;
if ( context = = null ) {
Log . e ( TAG , " error handling message, getContext() returned null " ) ;
return ;
}
2012-08-11 10:15:59 -07:00
switch ( msg . arg1 ) {
case COMMAND_CHANGE_TITLE :
2013-05-05 12:50:34 +02:00
if ( context instanceof Activity ) {
( ( Activity ) context ) . setTitle ( ( String ) msg . obj ) ;
} else {
Log . e ( TAG , " error handling message, getContext() returned no Activity " ) ;
}
2012-08-11 10:15:59 -07:00
break ;
2012-10-03 20:49:16 -07:00
case COMMAND_TEXTEDIT_HIDE :
if ( mTextEdit ! = null ) {
mTextEdit . setVisibility ( View . GONE ) ;
2013-05-05 12:50:34 +02:00
InputMethodManager imm = ( InputMethodManager ) context . getSystemService ( Context . INPUT_METHOD_SERVICE ) ;
2012-10-03 20:49:16 -07:00
imm . hideSoftInputFromWindow ( mTextEdit . getWindowToken ( ) , 0 ) ;
}
break ;
2013-05-05 12:53:57 +02:00
default :
2013-05-05 15:50:21 +02:00
if ( ( context instanceof SDLActivity ) & & ! ( ( SDLActivity ) context ) . onUnhandledMessage ( msg . arg1 , msg . obj ) ) {
2013-05-05 12:53:57 +02:00
Log . e ( TAG , " error handling message, command is " + msg . arg1 ) ;
}
2011-01-13 15:10:17 -08:00
}
}
2013-05-05 12:50:34 +02:00
}
// Handler for the messages
Handler commandHandler = new SDLCommandHandler ( ) ;
2011-01-13 15:10:17 -08:00
// Send a message from the SDLMain thread
2013-05-05 15:54:56 +02:00
boolean sendCommand ( int command , Object data ) {
2011-01-13 15:10:17 -08:00
Message msg = commandHandler . obtainMessage ( ) ;
msg . arg1 = command ;
msg . obj = data ;
2013-05-05 15:54:56 +02:00
return commandHandler . sendMessage ( msg ) ;
2011-01-13 15:10:17 -08:00
}
2011-01-06 17:12:31 -08:00
2011-01-12 14:29:01 -08:00
// C functions we call
2011-01-06 17:12:31 -08:00
public static native void nativeInit ( ) ;
2013-05-18 12:48:50 -07:00
public static native void nativeLowMemory ( ) ;
2011-01-06 17:12:31 -08:00
public static native void nativeQuit ( ) ;
2012-01-08 01:05:25 -05:00
public static native void nativePause ( ) ;
public static native void nativeResume ( ) ;
2011-01-13 11:14:20 -08:00
public static native void onNativeResize ( int x , int y , int format ) ;
2011-01-06 17:12:31 -08:00
public static native void onNativeKeyDown ( int keycode ) ;
public static native void onNativeKeyUp ( int keycode ) ;
2013-08-02 12:38:39 -03:00
public static native void onNativeKeyboardFocusLost ( ) ;
2011-10-13 01:21:35 -04:00
public static native void onNativeTouch ( int touchDevId , int pointerFingerId ,
int action , float x ,
2011-01-06 17:12:31 -08:00
float y , float p ) ;
2013-04-01 13:20:22 -03:00
public static native void onNativeAccel ( float x , float y , float z ) ;
2011-01-13 11:14:20 -08:00
public static native void nativeRunAudioThread ( ) ;
2011-01-06 17:12:31 -08:00
2011-01-12 14:53:23 -08:00
// Java functions called from C
2011-01-13 15:10:17 -08:00
2012-12-31 14:57:36 -08:00
public static boolean createGLContext ( int majorVersion , int minorVersion , int [ ] attribs ) {
return initEGL ( majorVersion , minorVersion , attribs ) ;
2011-01-06 17:12:31 -08:00
}
2011-01-12 14:29:01 -08:00
public static void flipBuffers ( ) {
2012-01-08 01:05:25 -05:00
flipEGL ( ) ;
2011-01-06 17:12:31 -08:00
}
2013-05-05 15:54:56 +02:00
public static boolean setActivityTitle ( String title ) {
2011-01-13 15:10:17 -08:00
// Called from SDLMain() thread and can't directly affect the view
2013-05-05 15:54:56 +02:00
return mSingleton . sendCommand ( COMMAND_CHANGE_TITLE , title ) ;
2011-01-13 15:10:17 -08:00
}
2013-05-05 15:54:56 +02:00
public static boolean sendMessage ( int command , int param ) {
return mSingleton . sendCommand ( command , Integer . valueOf ( param ) ) ;
2012-08-11 10:15:59 -07:00
}
2011-08-26 13:11:53 +01:00
public static Context getContext ( ) {
return mSingleton ;
}
2011-07-29 16:51:25 -04:00
2013-04-27 17:10:14 +02:00
static class ShowTextInputTask implements Runnable {
2012-10-03 20:49:16 -07:00
/ *
* This is used to regulate the pan & scan method to have some offset from
* the bottom edge of the input region and the top edge of an input
* method ( soft keyboard )
* /
static final int HEIGHT_PADDING = 15 ;
public int x , y , w , h ;
2013-04-27 17:10:14 +02:00
public ShowTextInputTask ( int x , int y , int w , int h ) {
2012-10-03 20:49:16 -07:00
this . x = x ;
this . y = y ;
this . w = w ;
this . h = h ;
}
2013-04-27 14:13:30 +02:00
@Override
2012-10-03 20:49:16 -07:00
public void run ( ) {
AbsoluteLayout . LayoutParams params = new AbsoluteLayout . LayoutParams (
w , h + HEIGHT_PADDING , x , y ) ;
if ( mTextEdit = = null ) {
mTextEdit = new DummyEdit ( getContext ( ) ) ;
mLayout . addView ( mTextEdit , params ) ;
} else {
mTextEdit . setLayoutParams ( params ) ;
}
mTextEdit . setVisibility ( View . VISIBLE ) ;
mTextEdit . requestFocus ( ) ;
InputMethodManager imm = ( InputMethodManager ) getContext ( ) . getSystemService ( Context . INPUT_METHOD_SERVICE ) ;
imm . showSoftInput ( mTextEdit , 0 ) ;
}
}
2013-05-05 15:54:56 +02:00
public static boolean showTextInput ( int x , int y , int w , int h ) {
2012-10-03 20:49:16 -07:00
// Transfer the task to the main thread as a Runnable
2013-05-05 15:54:56 +02:00
return mSingleton . commandHandler . post ( new ShowTextInputTask ( x , y , w , h ) ) ;
2012-10-03 20:49:16 -07:00
}
2012-01-08 01:05:25 -05:00
// EGL functions
2012-12-31 14:57:36 -08:00
public static boolean initEGL ( int majorVersion , int minorVersion , int [ ] attribs ) {
2012-11-04 08:51:43 -08:00
try {
if ( SDLActivity . mEGLDisplay = = null ) {
Log . v ( " SDL " , " Starting up OpenGL ES " + majorVersion + " . " + minorVersion ) ;
2012-01-08 01:05:25 -05:00
EGL10 egl = ( EGL10 ) EGLContext . getEGL ( ) ;
EGLDisplay dpy = egl . eglGetDisplay ( EGL10 . EGL_DEFAULT_DISPLAY ) ;
int [ ] version = new int [ 2 ] ;
egl . eglInitialize ( dpy , version ) ;
2013-07-26 12:22:40 -03:00
EGLConfig [ ] configs = new EGLConfig [ 128 ] ;
2012-01-08 01:05:25 -05:00
int [ ] num_config = new int [ 1 ] ;
2012-12-31 14:57:36 -08:00
if ( ! egl . eglChooseConfig ( dpy , attribs , configs , 1 , num_config ) | | num_config [ 0 ] = = 0 ) {
2012-01-08 01:05:25 -05:00
Log . e ( " SDL " , " No EGL config available " ) ;
return false ;
}
2013-07-26 12:22:40 -03:00
EGLConfig config = null ;
int bestdiff = - 1 , bitdiff ;
int [ ] value = new int [ 1 ] ;
// eglChooseConfig returns a number of configurations that match or exceed the requested attribs.
// From those, we select the one that matches our requirements more closely
Log . v ( " SDL " , " Got " + num_config [ 0 ] + " valid modes from egl " ) ;
for ( int i = 0 ; i < num_config [ 0 ] ; i + + ) {
bitdiff = 0 ;
// Go through some of the attributes and compute the bit difference between what we want and what we get.
for ( int j = 0 ; ; j + = 2 ) {
if ( attribs [ j ] = = EGL10 . EGL_NONE )
break ;
2013-07-26 12:37:36 -03:00
if ( attribs [ j + 1 ] ! = EGL10 . EGL_DONT_CARE & & ( attribs [ j ] = = EGL10 . EGL_RED_SIZE | |
2013-07-26 12:22:40 -03:00
attribs [ j ] = = EGL10 . EGL_GREEN_SIZE | |
attribs [ j ] = = EGL10 . EGL_BLUE_SIZE | |
attribs [ j ] = = EGL10 . EGL_ALPHA_SIZE | |
attribs [ j ] = = EGL10 . EGL_DEPTH_SIZE | |
2013-07-26 12:37:36 -03:00
attribs [ j ] = = EGL10 . EGL_STENCIL_SIZE ) ) {
2013-07-26 12:22:40 -03:00
egl . eglGetConfigAttrib ( dpy , configs [ i ] , attribs [ j ] , value ) ;
bitdiff + = value [ 0 ] - attribs [ j + 1 ] ; // value is always >= attrib
}
}
if ( bitdiff < bestdiff | | bestdiff = = - 1 ) {
config = configs [ i ] ;
bestdiff = bitdiff ;
}
if ( bitdiff = = 0 ) break ; // we found an exact match!
}
Log . d ( " SDL " , " Selected mode with a total bit difference of " + bestdiff ) ;
2012-01-08 01:05:25 -05:00
SDLActivity . mEGLDisplay = dpy ;
SDLActivity . mEGLConfig = config ;
SDLActivity . mGLMajor = majorVersion ;
SDLActivity . mGLMinor = minorVersion ;
2012-11-04 08:51:43 -08:00
}
return SDLActivity . createEGLSurface ( ) ;
2012-01-08 01:05:25 -05:00
2012-11-04 08:51:43 -08:00
} catch ( Exception e ) {
Log . v ( " SDL " , e + " " ) ;
for ( StackTraceElement s : e . getStackTrace ( ) ) {
Log . v ( " SDL " , s . toString ( ) ) ;
2012-01-08 01:05:25 -05:00
}
2012-11-04 08:51:43 -08:00
return false ;
2012-01-08 01:05:25 -05:00
}
}
public static boolean createEGLContext ( ) {
EGL10 egl = ( EGL10 ) EGLContext . getEGL ( ) ;
int EGL_CONTEXT_CLIENT_VERSION = 0x3098 ;
int contextAttrs [ ] = new int [ ] { EGL_CONTEXT_CLIENT_VERSION , SDLActivity . mGLMajor , EGL10 . EGL_NONE } ;
SDLActivity . mEGLContext = egl . eglCreateContext ( SDLActivity . mEGLDisplay , SDLActivity . mEGLConfig , EGL10 . EGL_NO_CONTEXT , contextAttrs ) ;
if ( SDLActivity . mEGLContext = = EGL10 . EGL_NO_CONTEXT ) {
Log . e ( " SDL " , " Couldn't create context " ) ;
return false ;
}
return true ;
}
public static boolean createEGLSurface ( ) {
if ( SDLActivity . mEGLDisplay ! = null & & SDLActivity . mEGLConfig ! = null ) {
EGL10 egl = ( EGL10 ) EGLContext . getEGL ( ) ;
if ( SDLActivity . mEGLContext = = null ) createEGLContext ( ) ;
2013-07-06 15:22:49 -03:00
if ( SDLActivity . mEGLSurface = = EGL10 . EGL_NO_SURFACE ) {
Log . v ( " SDL " , " Creating new EGL Surface " ) ;
SDLActivity . mEGLSurface = egl . eglCreateWindowSurface ( SDLActivity . mEGLDisplay , SDLActivity . mEGLConfig , SDLActivity . mSurface , null ) ;
if ( SDLActivity . mEGLSurface = = EGL10 . EGL_NO_SURFACE ) {
Log . e ( " SDL " , " Couldn't create surface " ) ;
return false ;
}
2012-01-08 01:05:25 -05:00
}
2013-07-06 15:22:49 -03:00
else Log . v ( " SDL " , " EGL Surface remains valid " ) ;
2012-01-08 01:05:25 -05:00
2012-06-19 13:57:42 -03:00
if ( egl . eglGetCurrentContext ( ) ! = SDLActivity . mEGLContext ) {
2013-07-06 15:22:49 -03:00
if ( ! egl . eglMakeCurrent ( SDLActivity . mEGLDisplay , SDLActivity . mEGLSurface , SDLActivity . mEGLSurface , SDLActivity . mEGLContext ) ) {
2012-06-19 13:57:42 -03:00
Log . e ( " SDL " , " Old EGL Context doesnt work, trying with a new one " ) ;
// TODO: Notify the user via a message that the old context could not be restored, and that textures need to be manually restored.
createEGLContext ( ) ;
2013-07-06 15:22:49 -03:00
if ( ! egl . eglMakeCurrent ( SDLActivity . mEGLDisplay , SDLActivity . mEGLSurface , SDLActivity . mEGLSurface , SDLActivity . mEGLContext ) ) {
2012-06-19 13:57:42 -03:00
Log . e ( " SDL " , " Failed making EGL Context current " ) ;
return false ;
}
2012-01-08 01:05:25 -05:00
}
2013-07-06 15:22:49 -03:00
else Log . v ( " SDL " , " EGL Context made current " ) ;
2012-01-08 01:05:25 -05:00
}
2013-07-06 15:22:49 -03:00
else Log . v ( " SDL " , " EGL Context remains current " ) ;
2012-01-08 01:05:25 -05:00
return true ;
2012-11-04 08:51:43 -08:00
} else {
Log . e ( " SDL " , " Surface creation failed, display = " + SDLActivity . mEGLDisplay + " , config = " + SDLActivity . mEGLConfig ) ;
return false ;
2012-01-08 01:05:25 -05:00
}
}
// EGL buffer flip
public static void flipEGL ( ) {
try {
EGL10 egl = ( EGL10 ) EGLContext . getEGL ( ) ;
egl . eglWaitNative ( EGL10 . EGL_CORE_NATIVE_ENGINE , null ) ;
// drawing here
egl . eglWaitGL ( ) ;
egl . eglSwapBuffers ( SDLActivity . mEGLDisplay , SDLActivity . mEGLSurface ) ;
} catch ( Exception e ) {
Log . v ( " SDL " , " flipEGL(): " + e ) ;
for ( StackTraceElement s : e . getStackTrace ( ) ) {
Log . v ( " SDL " , s . toString ( ) ) ;
}
}
}
2011-01-13 11:14:20 -08:00
// Audio
2013-07-31 10:04:59 -03:00
public static int audioInit ( int sampleRate , boolean is16Bit , boolean isStereo , int desiredFrames ) {
2011-01-13 11:14:20 -08:00
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 ) ;
2013-04-27 17:23:20 +02:00
Log . v ( " SDL " , " SDL audio: wanted " + ( isStereo ? " stereo " : " mono " ) + " " + ( is16Bit ? " 16-bit " : " 8-bit " ) + " " + ( sampleRate / 1000f ) + " kHz, " + desiredFrames + " frames buffer " ) ;
2011-01-13 11:14:20 -08:00
// 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 ) ;
mAudioTrack = new AudioTrack ( AudioManager . STREAM_MUSIC , sampleRate ,
channelConfig , audioFormat , desiredFrames * frameSize , AudioTrack . MODE_STREAM ) ;
2013-07-31 10:04:59 -03:00
// 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 ( " SDL " , " Failed during initialization of Audio Track " ) ;
2013-07-31 16:25:50 -03:00
mAudioTrack = null ;
2013-07-31 10:04:59 -03:00
return - 1 ;
}
2011-01-13 11:14:20 -08:00
audioStartThread ( ) ;
2013-04-27 17:23:20 +02:00
Log . v ( " SDL " , " 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 " ) ;
2013-07-31 10:04:59 -03:00
return 0 ;
2011-01-13 11:14:20 -08:00
}
public static void audioStartThread ( ) {
mAudioThread = new Thread ( new Runnable ( ) {
2013-04-27 14:13:30 +02:00
@Override
2011-01-13 11:14:20 -08:00
public void run ( ) {
mAudioTrack . play ( ) ;
nativeRunAudioThread ( ) ;
}
} ) ;
2011-01-06 17:12:31 -08:00
2011-01-13 11:14:20 -08:00
// I'd take REALTIME if I could get it!
mAudioThread . setPriority ( Thread . MAX_PRIORITY ) ;
mAudioThread . start ( ) ;
}
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 {
2011-01-13 12:32:55 -08:00
Thread . sleep ( 1 ) ;
2011-01-13 11:14:20 -08:00
} catch ( InterruptedException e ) {
// Nom nom
}
} else {
Log . w ( " SDL " , " SDL audio: error return from write(short) " ) ;
return ;
}
}
}
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 {
2011-01-13 12:32:55 -08:00
Thread . sleep ( 1 ) ;
2011-01-13 11:14:20 -08:00
} catch ( InterruptedException e ) {
// Nom nom
}
} else {
2013-04-27 13:33:05 +02:00
Log . w ( " SDL " , " SDL audio: error return from write(byte) " ) ;
2011-01-13 11:14:20 -08:00
return ;
}
}
2011-01-06 17:12:31 -08:00
}
2011-01-13 12:32:55 -08:00
public static void audioQuit ( ) {
if ( mAudioThread ! = null ) {
try {
mAudioThread . join ( ) ;
} catch ( Exception e ) {
Log . v ( " SDL " , " Problem stopping audio thread: " + e ) ;
}
mAudioThread = null ;
2011-01-13 15:10:17 -08:00
//Log.v("SDL", "Finished waiting for audio thread");
2011-01-13 12:32:55 -08:00
}
if ( mAudioTrack ! = null ) {
mAudioTrack . stop ( ) ;
mAudioTrack = null ;
}
}
2011-01-06 17:12:31 -08:00
}
/ * *
Simple nativeInit ( ) runnable
* /
2011-01-12 14:29:01 -08:00
class SDLMain implements Runnable {
2013-04-27 14:13:30 +02:00
@Override
2011-01-12 14:29:01 -08:00
public void run ( ) {
// Runs SDL_main()
2011-01-06 17:12:31 -08:00
SDLActivity . nativeInit ( ) ;
2011-01-12 17:53:06 -08:00
//Log.v("SDL", "SDL thread terminated");
2011-01-06 17:12:31 -08:00
}
}
/ * *
SDLSurface . This is what we draw on , so we need to know when it ' s created
in order to do anything useful .
Because of this , that ' s where we set up the SDL thread
* /
class SDLSurface extends SurfaceView implements SurfaceHolder . Callback ,
View . OnKeyListener , View . OnTouchListener , SensorEventListener {
2011-01-12 14:29:01 -08:00
// Sensors
2013-05-15 23:18:29 -07:00
protected static SensorManager mSensorManager ;
2011-01-06 17:12:31 -08:00
2012-08-24 13:10:29 -03:00
// Keep track of the surface size to normalize touch events
2013-05-15 23:18:29 -07:00
protected static float mWidth , mHeight ;
2012-08-24 13:10:29 -03:00
2011-01-12 14:29:01 -08:00
// Startup
2011-01-06 17:12:31 -08:00
public SDLSurface ( Context context ) {
super ( context ) ;
getHolder ( ) . addCallback ( this ) ;
setFocusable ( true ) ;
setFocusableInTouchMode ( true ) ;
requestFocus ( ) ;
setOnKeyListener ( this ) ;
2013-04-01 13:20:22 -03:00
setOnTouchListener ( this ) ;
2011-01-12 14:29:01 -08:00
2013-04-27 14:26:15 +02:00
mSensorManager = ( SensorManager ) context . getSystemService ( Context . SENSOR_SERVICE ) ;
2012-08-24 13:10:29 -03:00
// Some arbitrary defaults to avoid a potential division by zero
mWidth = 1 . 0f ;
mHeight = 1 . 0f ;
2011-01-06 17:12:31 -08:00
}
2011-01-12 14:29:01 -08:00
// Called when we have a valid drawing surface
2013-04-27 14:13:30 +02:00
@Override
2011-01-06 17:12:31 -08:00
public void surfaceCreated ( SurfaceHolder holder ) {
2012-01-08 01:05:25 -05:00
Log . v ( " SDL " , " surfaceCreated() " ) ;
holder . setType ( SurfaceHolder . SURFACE_TYPE_GPU ) ;
2013-07-06 15:22:49 -03:00
// Set mIsSurfaceReady to 'true' *before* any call to handleResume
SDLActivity . mIsSurfaceReady = true ;
2011-01-06 17:12:31 -08:00
}
2011-01-12 14:29:01 -08:00
// Called when we lose the surface
2013-04-27 14:13:30 +02:00
@Override
2011-01-06 17:12:31 -08:00
public void surfaceDestroyed ( SurfaceHolder holder ) {
2012-01-08 01:05:25 -05:00
Log . v ( " SDL " , " surfaceDestroyed() " ) ;
2013-07-06 15:22:49 -03:00
// Call this *before* setting mIsSurfaceReady to 'false'
SDLActivity . handlePause ( ) ;
SDLActivity . mIsSurfaceReady = false ;
/ * We have to clear the current context and destroy the egl surface here
* Otherwise there ' s BAD_NATIVE_WINDOW errors coming from eglCreateWindowSurface on resume
* Ref : http : //stackoverflow.com/questions/8762589/eglcreatewindowsurface-on-ics-and-switching-from-2d-to-3d
* /
EGL10 egl = ( EGL10 ) EGLContext . getEGL ( ) ;
egl . eglMakeCurrent ( SDLActivity . mEGLDisplay , EGL10 . EGL_NO_SURFACE , EGL10 . EGL_NO_SURFACE , EGL10 . EGL_NO_CONTEXT ) ;
egl . eglDestroySurface ( SDLActivity . mEGLDisplay , SDLActivity . mEGLSurface ) ;
SDLActivity . mEGLSurface = EGL10 . EGL_NO_SURFACE ;
2011-01-06 17:12:31 -08:00
}
2011-01-12 14:29:01 -08:00
// Called when the surface is resized
2013-04-27 14:13:30 +02:00
@Override
2011-01-12 14:29:01 -08:00
public void surfaceChanged ( SurfaceHolder holder ,
int format , int width , int height ) {
2012-01-08 01:05:25 -05:00
Log . v ( " SDL " , " surfaceChanged() " ) ;
2011-01-12 14:29:01 -08:00
2013-03-03 21:52:51 -08:00
int sdlFormat = 0x15151002 ; // SDL_PIXELFORMAT_RGB565 by default
2011-01-12 14:29:01 -08:00
switch ( format ) {
case PixelFormat . A_8 :
2011-01-12 17:53:06 -08:00
Log . v ( " SDL " , " pixel format A_8 " ) ;
2011-01-12 14:29:01 -08:00
break ;
case PixelFormat . LA_88 :
2011-01-12 17:53:06 -08:00
Log . v ( " SDL " , " pixel format LA_88 " ) ;
2011-01-12 14:29:01 -08:00
break ;
case PixelFormat . L_8 :
2011-01-12 17:53:06 -08:00
Log . v ( " SDL " , " pixel format L_8 " ) ;
2011-01-12 14:29:01 -08:00
break ;
case PixelFormat . RGBA_4444 :
2011-01-12 17:53:06 -08:00
Log . v ( " SDL " , " pixel format RGBA_4444 " ) ;
2013-03-03 21:52:51 -08:00
sdlFormat = 0x15421002 ; // SDL_PIXELFORMAT_RGBA4444
2011-01-12 14:29:01 -08:00
break ;
case PixelFormat . RGBA_5551 :
2011-01-12 17:53:06 -08:00
Log . v ( " SDL " , " pixel format RGBA_5551 " ) ;
2013-03-03 21:52:51 -08:00
sdlFormat = 0x15441002 ; // SDL_PIXELFORMAT_RGBA5551
2011-01-12 14:29:01 -08:00
break ;
case PixelFormat . RGBA_8888 :
2011-01-12 17:53:06 -08:00
Log . v ( " SDL " , " pixel format RGBA_8888 " ) ;
2013-03-03 21:52:51 -08:00
sdlFormat = 0x16462004 ; // SDL_PIXELFORMAT_RGBA8888
2011-01-12 14:29:01 -08:00
break ;
case PixelFormat . RGBX_8888 :
2011-01-12 17:53:06 -08:00
Log . v ( " SDL " , " pixel format RGBX_8888 " ) ;
2013-03-03 21:52:51 -08:00
sdlFormat = 0x16261804 ; // SDL_PIXELFORMAT_RGBX8888
2011-01-12 14:29:01 -08:00
break ;
case PixelFormat . RGB_332 :
2011-01-12 17:53:06 -08:00
Log . v ( " SDL " , " pixel format RGB_332 " ) ;
2013-03-03 21:52:51 -08:00
sdlFormat = 0x14110801 ; // SDL_PIXELFORMAT_RGB332
2011-01-12 14:29:01 -08:00
break ;
case PixelFormat . RGB_565 :
2011-01-12 17:53:06 -08:00
Log . v ( " SDL " , " pixel format RGB_565 " ) ;
2013-03-03 21:52:51 -08:00
sdlFormat = 0x15151002 ; // SDL_PIXELFORMAT_RGB565
2011-01-12 14:29:01 -08:00
break ;
case PixelFormat . RGB_888 :
2011-01-12 17:53:06 -08:00
Log . v ( " SDL " , " pixel format RGB_888 " ) ;
2011-01-12 14:29:01 -08:00
// Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
2013-03-03 21:52:51 -08:00
sdlFormat = 0x16161804 ; // SDL_PIXELFORMAT_RGB888
2011-01-12 14:29:01 -08:00
break ;
2011-01-12 19:33:29 -08:00
default :
Log . v ( " SDL " , " pixel format unknown " + format ) ;
break ;
2011-01-12 14:29:01 -08:00
}
2012-08-24 13:10:29 -03:00
2013-04-27 17:23:20 +02:00
mWidth = width ;
mHeight = height ;
2011-01-12 16:35:03 -08:00
SDLActivity . onNativeResize ( width , height , sdlFormat ) ;
2012-01-08 01:05:25 -05:00
Log . v ( " SDL " , " Window size: " + width + " x " + height ) ;
2011-01-12 14:29:01 -08:00
2013-07-06 15:22:49 -03:00
// Set mIsSurfaceReady to 'true' *before* making a call to handleResume
SDLActivity . mIsSurfaceReady = true ;
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
SDLActivity . mSDLThread = new Thread ( new SDLMain ( ) , " SDLThread " ) ;
enableSensor ( Sensor . TYPE_ACCELEROMETER , true ) ;
SDLActivity . mSDLThread . start ( ) ;
} else {
// The app already exists, we resume via handleResume
// Multiple sequential calls to surfaceChanged are handled internally by handleResume
SDLActivity . handleResume ( ) ;
}
2011-01-06 17:12:31 -08:00
}
2011-01-12 14:53:23 -08:00
// unused
2013-04-27 14:13:30 +02:00
@Override
2011-01-06 17:12:31 -08:00
public void onDraw ( Canvas canvas ) { }
2011-01-12 14:29:01 -08:00
// Key events
2013-04-27 14:13:30 +02:00
@Override
2011-01-12 17:53:06 -08:00
public boolean onKey ( View v , int keyCode , KeyEvent event ) {
2013-04-01 13:20:22 -03:00
2013-07-31 21:08:22 -07:00
// Ignore volume keys so they're handled by Android
if ( keyCode = = KeyEvent . KEYCODE_VOLUME_DOWN | |
keyCode = = KeyEvent . KEYCODE_VOLUME_UP ) {
return false ;
}
2013-04-01 13:20:22 -03:00
if ( event . getAction ( ) = = KeyEvent . ACTION_DOWN ) {
//Log.v("SDL", "key down: " + keyCode);
SDLActivity . onNativeKeyDown ( keyCode ) ;
return true ;
}
else if ( event . getAction ( ) = = KeyEvent . ACTION_UP ) {
//Log.v("SDL", "key up: " + keyCode);
SDLActivity . onNativeKeyUp ( keyCode ) ;
return true ;
2011-01-06 17:12:31 -08:00
}
return false ;
}
2011-01-12 14:29:01 -08:00
// Touch events
2013-04-27 14:13:30 +02:00
@Override
2011-01-12 14:29:01 -08:00
public boolean onTouch ( View v , MotionEvent event ) {
2011-10-13 01:21:35 -04:00
final int touchDevId = event . getDeviceId ( ) ;
final int pointerCount = event . getPointerCount ( ) ;
// touchId, pointerId, action, x, y, pressure
2013-05-05 16:01:19 +02:00
int actionPointerIndex = ( event . getAction ( ) & MotionEvent . ACTION_POINTER_ID_MASK ) > > MotionEvent . ACTION_POINTER_ID_SHIFT ; /* API 8: event.getActionIndex(); */
2011-10-13 01:21:35 -04:00
int pointerFingerId = event . getPointerId ( actionPointerIndex ) ;
2012-08-03 23:59:05 -04:00
int action = ( event . getAction ( ) & MotionEvent . ACTION_MASK ) ; /* API 8: event.getActionMasked(); */
2011-10-13 01:21:35 -04:00
2012-08-24 13:10:29 -03:00
float x = event . getX ( actionPointerIndex ) / mWidth ;
float y = event . getY ( actionPointerIndex ) / mHeight ;
2011-10-13 01:21:35 -04:00
float p = event . getPressure ( actionPointerIndex ) ;
if ( action = = MotionEvent . ACTION_MOVE & & pointerCount > 1 ) {
// TODO send motion to every pointer if its position has
// changed since prev event.
for ( int i = 0 ; i < pointerCount ; i + + ) {
pointerFingerId = event . getPointerId ( i ) ;
2012-08-24 13:10:29 -03:00
x = event . getX ( i ) / mWidth ;
y = event . getY ( i ) / mHeight ;
2011-10-13 01:21:35 -04:00
p = event . getPressure ( i ) ;
SDLActivity . onNativeTouch ( touchDevId , pointerFingerId , action , x , y , p ) ;
}
} else {
SDLActivity . onNativeTouch ( touchDevId , pointerFingerId , action , x , y , p ) ;
}
return true ;
2013-04-01 13:20:22 -03:00
}
2011-01-06 17:12:31 -08:00
2011-01-12 14:29:01 -08:00
// Sensor events
public void enableSensor ( int sensortype , boolean enabled ) {
2011-01-12 14:53:23 -08:00
// TODO: This uses getDefaultSensor - what if we have >1 accels?
2011-01-12 14:29:01 -08:00
if ( enabled ) {
2011-01-06 17:12:31 -08:00
mSensorManager . registerListener ( this ,
mSensorManager . getDefaultSensor ( sensortype ) ,
SensorManager . SENSOR_DELAY_GAME , null ) ;
2011-01-12 14:29:01 -08:00
} else {
2011-01-06 17:12:31 -08:00
mSensorManager . unregisterListener ( this ,
mSensorManager . getDefaultSensor ( sensortype ) ) ;
}
}
2013-04-27 14:13:30 +02:00
@Override
2011-01-12 14:29:01 -08:00
public void onAccuracyChanged ( Sensor sensor , int accuracy ) {
2011-01-12 14:53:23 -08:00
// TODO
2011-01-06 17:12:31 -08:00
}
2013-04-27 14:13:30 +02:00
@Override
2011-01-12 14:29:01 -08:00
public void onSensorChanged ( SensorEvent event ) {
if ( event . sensor . getType ( ) = = Sensor . TYPE_ACCELEROMETER ) {
2012-01-13 20:57:35 -05:00
SDLActivity . onNativeAccel ( event . values [ 0 ] / SensorManager . GRAVITY_EARTH ,
event . values [ 1 ] / SensorManager . GRAVITY_EARTH ,
event . values [ 2 ] / SensorManager . GRAVITY_EARTH ) ;
2011-01-06 17:12:31 -08:00
}
}
2013-04-01 13:20:22 -03:00
2012-10-03 20:49:16 -07:00
}
/ * This is a fake invisible editor view that receives the input and defines the
* pan & scan region
* /
class DummyEdit extends View implements View . OnKeyListener {
InputConnection ic ;
public DummyEdit ( Context context ) {
super ( context ) ;
setFocusableInTouchMode ( true ) ;
setFocusable ( true ) ;
setOnKeyListener ( this ) ;
}
@Override
public boolean onCheckIsTextEditor ( ) {
return true ;
}
2013-04-27 14:13:30 +02:00
@Override
2012-10-03 20:49:16 -07:00
public boolean onKey ( View v , int keyCode , KeyEvent event ) {
// This handles the hardware keyboard input
if ( event . isPrintingKey ( ) ) {
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 ;
}
2013-08-02 12:38:39 -03:00
//
@Override
public boolean onKeyPreIme ( int keyCode , KeyEvent event ) {
// As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
// FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
// FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
// FIXME: A more effective solution would be to change our Layout from AbsoluteLayout to Relative or Linear
// FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
// FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
if ( event . getAction ( ) = = KeyEvent . ACTION_UP & & keyCode = = KeyEvent . KEYCODE_BACK ) {
if ( SDLActivity . mTextEdit ! = null & & SDLActivity . mTextEdit . getVisibility ( ) = = View . VISIBLE ) {
SDLActivity . onNativeKeyboardFocusLost ( ) ;
}
}
return super . onKeyPreIme ( keyCode , event ) ;
}
2012-10-03 20:49:16 -07:00
@Override
public InputConnection onCreateInputConnection ( EditorInfo outAttrs ) {
ic = new SDLInputConnection ( this , true ) ;
outAttrs . imeOptions = EditorInfo . IME_FLAG_NO_EXTRACT_UI
2012-11-02 02:37:49 -07:00
| 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */ ;
2012-10-03 20:49:16 -07:00
return ic ;
}
}
class SDLInputConnection extends BaseInputConnection {
public SDLInputConnection ( View targetView , boolean fullEditor ) {
super ( targetView , fullEditor ) ;
}
@Override
public boolean sendKeyEvent ( KeyEvent event ) {
/ *
* This handles the keycodes from soft keyboard ( and IME - translated
* input from hardkeyboard )
* /
int keyCode = event . getKeyCode ( ) ;
if ( event . getAction ( ) = = KeyEvent . ACTION_DOWN ) {
2013-01-26 12:31:55 -03:00
if ( event . isPrintingKey ( ) ) {
commitText ( String . valueOf ( ( char ) event . getUnicodeChar ( ) ) , 1 ) ;
}
2012-10-03 20:49:16 -07:00
SDLActivity . onNativeKeyDown ( keyCode ) ;
return true ;
} else if ( event . getAction ( ) = = KeyEvent . ACTION_UP ) {
SDLActivity . onNativeKeyUp ( keyCode ) ;
return true ;
}
return super . sendKeyEvent ( event ) ;
}
@Override
public boolean commitText ( CharSequence text , int newCursorPosition ) {
nativeCommitText ( text . toString ( ) , newCursorPosition ) ;
return super . commitText ( text , newCursorPosition ) ;
}
@Override
public boolean setComposingText ( CharSequence text , int newCursorPosition ) {
nativeSetComposingText ( text . toString ( ) , newCursorPosition ) ;
return super . setComposingText ( text , newCursorPosition ) ;
}
public native void nativeCommitText ( String text , int newCursorPosition ) ;
public native void nativeSetComposingText ( String text , int newCursorPosition ) ;
2011-01-06 17:12:31 -08:00
}