2015-04-20 10:22:44 -06:00
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.* ;
import android.content.* ;
import android.view.* ;
import android.view.inputmethod.BaseInputConnection ;
import android.view.inputmethod.EditorInfo ;
import android.view.inputmethod.InputConnection ;
import android.view.inputmethod.InputMethodManager ;
import android.widget.AbsoluteLayout ;
import android.widget.Button ;
import android.widget.LinearLayout ;
import android.widget.TextView ;
import android.os.* ;
import android.util.Log ;
import android.util.SparseArray ;
import android.graphics.* ;
import android.graphics.drawable.Drawable ;
import android.media.* ;
import android.hardware.* ;
/ * *
SDL Activity
* /
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 mExitCalledFromJava ;
/** If shared libraries (e.g. SDL or the native application) could not be loaded. */
public static boolean mBrokenLibraries ;
// If we want to separate mouse and touch events.
// This is only toggled in native code when a hint is set!
public static boolean mSeparateMouseAndTouch ;
// Main components
protected static SDLActivity mSingleton ;
protected static SDLSurface mSurface ;
protected static View mTextEdit ;
protected static ViewGroup mLayout ;
protected static SDLJoystickHandler mJoystickHandler ;
// This is what SDL runs in. It invokes SDL_main(), eventually
protected static Thread mSDLThread ;
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
// Audio
protected static AudioTrack mAudioTrack ;
/ * *
* This method is called by SDL before loading the native shared libraries .
* It can be overridden to provide names of shared libraries to be loaded .
* The default implementation returns the defaults . It never returns null .
* An array returned by a new implementation must at least contain "SDL2" .
* Also keep in mind that the order the libraries are loaded may matter .
* @return names of shared libraries to be loaded ( e . g . "SDL2" , "main" ) .
* /
protected String [ ] getLibraries ( ) {
return new String [ ] {
"SDL2" ,
// "SDL2_image",
// "SDL2_mixer",
// "SDL2_net",
// "SDL2_ttf",
"main"
} ;
}
// Load the .so
public void loadLibraries ( ) {
for ( String lib : getLibraries ( ) ) {
System . loadLibrary ( lib ) ;
}
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
/ * *
* This method is called by SDL before starting the native application thread .
* It can be overridden to provide the arguments after the application name .
* The default implementation returns an empty array . It never returns null .
* @return arguments for the native application .
* /
protected String [ ] getArguments ( ) {
return new String [ 0 ] ;
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
public static void initialize ( ) {
// The static nature of the singleton and Android quirkyness force us to initialize everything here
// Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
mSingleton = null ;
mSurface = null ;
mTextEdit = null ;
mLayout = null ;
mJoystickHandler = null ;
mSDLThread = null ;
mAudioTrack = null ;
mExitCalledFromJava = false ;
mBrokenLibraries = false ;
mIsPaused = false ;
mIsSurfaceReady = false ;
mHasFocus = true ;
}
// Setup
@Override
protected void onCreate ( Bundle savedInstanceState ) {
2015-05-16 13:15:59 -06:00
Log . v ( TAG , "Device: " + android . os . Build . DEVICE ) ;
Log . v ( TAG , "Model: " + android . os . Build . MODEL ) ;
Log . v ( TAG , "onCreate():" + mSingleton ) ;
2015-04-20 10:22:44 -06:00
super . onCreate ( savedInstanceState ) ;
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
SDLActivity . initialize ( ) ;
// So we can call stuff from static callbacks
mSingleton = this ;
// Load shared libraries
String errorMsgBrokenLib = "" ;
try {
loadLibraries ( ) ;
} catch ( UnsatisfiedLinkError e ) {
System . err . println ( e . getMessage ( ) ) ;
mBrokenLibraries = true ;
errorMsgBrokenLib = e . getMessage ( ) ;
} catch ( Exception e ) {
System . err . println ( e . getMessage ( ) ) ;
mBrokenLibraries = true ;
errorMsgBrokenLib = e . getMessage ( ) ;
}
if ( mBrokenLibraries )
{
AlertDialog . Builder dlgAlert = new AlertDialog . Builder ( this ) ;
dlgAlert . setMessage ( "An error occurred while trying to start the application. Please try again and/or reinstall."
+ System . getProperty ( "line.separator" )
+ System . getProperty ( "line.separator" )
+ "Error: " + errorMsgBrokenLib ) ;
dlgAlert . setTitle ( "SDL Error" ) ;
dlgAlert . setPositiveButton ( "Exit" ,
new DialogInterface . OnClickListener ( ) {
@Override
public void onClick ( DialogInterface dialog , int id ) {
// if this button is clicked, close current activity
SDLActivity . mSingleton . finish ( ) ;
}
} ) ;
dlgAlert . setCancelable ( false ) ;
dlgAlert . create ( ) . show ( ) ;
return ;
}
// Set up the surface
mSurface = new SDLSurface ( getApplication ( ) ) ;
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
if ( Build . VERSION . SDK_INT > = 12 ) {
mJoystickHandler = new SDLJoystickHandler_API12 ( ) ;
}
else {
mJoystickHandler = new SDLJoystickHandler ( ) ;
}
mLayout = new AbsoluteLayout ( this ) ;
mLayout . addView ( mSurface ) ;
setContentView ( mLayout ) ;
}
// Events
@Override
protected void onPause ( ) {
2015-05-16 13:15:59 -06:00
Log . v ( TAG , "onPause()" ) ;
2015-04-20 10:22:44 -06:00
super . onPause ( ) ;
if ( SDLActivity . mBrokenLibraries ) {
return ;
}
SDLActivity . handlePause ( ) ;
}
@Override
protected void onResume ( ) {
2015-05-16 13:15:59 -06:00
Log . v ( TAG , "onResume()" ) ;
2015-04-20 10:22:44 -06:00
super . onResume ( ) ;
if ( SDLActivity . mBrokenLibraries ) {
return ;
}
SDLActivity . handleResume ( ) ;
}
@Override
public void onWindowFocusChanged ( boolean hasFocus ) {
super . onWindowFocusChanged ( hasFocus ) ;
2015-05-16 13:15:59 -06:00
Log . v ( TAG , "onWindowFocusChanged(): " + hasFocus ) ;
2015-04-20 10:22:44 -06:00
if ( SDLActivity . mBrokenLibraries ) {
return ;
}
SDLActivity . mHasFocus = hasFocus ;
if ( hasFocus ) {
SDLActivity . handleResume ( ) ;
}
}
@Override
public void onLowMemory ( ) {
2015-05-16 13:15:59 -06:00
Log . v ( TAG , "onLowMemory()" ) ;
2015-04-20 10:22:44 -06:00
super . onLowMemory ( ) ;
if ( SDLActivity . mBrokenLibraries ) {
return ;
}
SDLActivity . nativeLowMemory ( ) ;
}
@Override
protected void onDestroy ( ) {
2015-05-16 13:15:59 -06:00
Log . v ( TAG , "onDestroy()" ) ;
2015-04-20 10:22:44 -06:00
if ( SDLActivity . mBrokenLibraries ) {
super . onDestroy ( ) ;
// Reset everything in case the user re opens the app
SDLActivity . initialize ( ) ;
return ;
}
// Send a quit message to the application
SDLActivity . mExitCalledFromJava = true ;
SDLActivity . nativeQuit ( ) ;
// Now wait for the SDL thread to quit
if ( SDLActivity . mSDLThread ! = null ) {
try {
SDLActivity . mSDLThread . join ( ) ;
} catch ( Exception e ) {
2015-05-16 13:15:59 -06:00
Log . v ( TAG , "Problem stopping thread: " + e ) ;
2015-04-20 10:22:44 -06:00
}
SDLActivity . mSDLThread = null ;
2015-05-16 13:15:59 -06:00
//Log.v(TAG, "Finished waiting for SDL thread");
2015-04-20 10:22:44 -06:00
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
super . onDestroy ( ) ;
// Reset everything in case the user re opens the app
SDLActivity . initialize ( ) ;
}
@Override
public boolean dispatchKeyEvent ( KeyEvent event ) {
if ( SDLActivity . mBrokenLibraries ) {
return false ;
}
int keyCode = event . getKeyCode ( ) ;
// Ignore certain special keys so they're handled by Android
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 */
) {
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 . 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 ( ) {
if ( SDLActivity . mIsPaused & & SDLActivity . mIsSurfaceReady & & SDLActivity . mHasFocus ) {
SDLActivity . mIsPaused = false ;
SDLActivity . nativeResume ( ) ;
mSurface . handleResume ( ) ;
}
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
/* The native thread has finished */
public static void handleNativeExit ( ) {
SDLActivity . mSDLThread = null ;
mSingleton . finish ( ) ;
}
// Messages from the SDLMain thread
static final int COMMAND_CHANGE_TITLE = 1 ;
static final int COMMAND_UNUSED = 2 ;
static final int COMMAND_TEXTEDIT_HIDE = 3 ;
static final int COMMAND_SET_KEEP_SCREEN_ON = 5 ;
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 .
* @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 .
* /
protected boolean onUnhandledMessage ( int command , Object param ) {
return false ;
}
/ * *
* A Handler class for Messages from native SDL applications .
* It uses current Activities as target ( e . g . for the title ) .
* static to prevent implicit references to enclosing object .
* /
protected static class SDLCommandHandler extends Handler {
@Override
public void handleMessage ( Message msg ) {
Context context = getContext ( ) ;
if ( context = = null ) {
Log . e ( TAG , "error handling message, getContext() returned null" ) ;
return ;
}
switch ( msg . arg1 ) {
case COMMAND_CHANGE_TITLE :
if ( context instanceof Activity ) {
( ( Activity ) context ) . setTitle ( ( String ) msg . obj ) ;
} else {
Log . e ( TAG , "error handling message, getContext() returned no Activity" ) ;
}
break ;
case COMMAND_TEXTEDIT_HIDE :
if ( mTextEdit ! = null ) {
mTextEdit . setVisibility ( View . GONE ) ;
InputMethodManager imm = ( InputMethodManager ) context . getSystemService ( Context . INPUT_METHOD_SERVICE ) ;
imm . hideSoftInputFromWindow ( mTextEdit . getWindowToken ( ) , 0 ) ;
}
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 ) ;
}
}
break ;
}
default :
if ( ( context instanceof SDLActivity ) & & ! ( ( SDLActivity ) context ) . onUnhandledMessage ( msg . arg1 , msg . obj ) ) {
Log . e ( TAG , "error handling message, command is " + msg . arg1 ) ;
}
}
}
}
// Handler for the messages
Handler commandHandler = new SDLCommandHandler ( ) ;
// Send a message from the SDLMain thread
boolean sendCommand ( int command , Object data ) {
Message msg = commandHandler . obtainMessage ( ) ;
msg . arg1 = command ;
msg . obj = data ;
return commandHandler . sendMessage ( msg ) ;
}
// C functions we call
public static native int nativeInit ( Object arguments ) ;
public static native void nativeLowMemory ( ) ;
public static native void nativeQuit ( ) ;
public static native void nativePause ( ) ;
public static native void nativeResume ( ) ;
public static native void 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 ( ) ;
public static native void onNativeMouse ( int button , int action , float x , float y ) ;
public static native void onNativeTouch ( int touchDevId , int pointerFingerId ,
2015-04-08 04:14:36 -06:00
int action , float x ,
2015-04-20 10:22:44 -06:00
float y , float p ) ;
public static native void onNativeAccel ( float x , float y , float z ) ;
public static native void onNativeSurfaceChanged ( ) ;
public static native void onNativeSurfaceDestroyed ( ) ;
public static native void nativeFlipBuffers ( ) ;
2015-04-08 04:14:36 -06:00
public static native int nativeAddJoystick ( int device_id , String name ,
int is_accelerometer , int nbuttons ,
2015-04-20 10:22:44 -06:00
int naxes , int nhats , int nballs ) ;
public static native int nativeRemoveJoystick ( int device_id ) ;
public static native String nativeGetHint ( String name ) ;
/ * *
* This method is called by SDL using JNI .
* /
public static void flipBuffers ( ) {
SDLActivity . nativeFlipBuffers ( ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean setActivityTitle ( String title ) {
// Called from SDLMain() thread and can't directly affect the view
return mSingleton . sendCommand ( COMMAND_CHANGE_TITLE , title ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean sendMessage ( int command , int param ) {
return mSingleton . sendCommand ( command , Integer . valueOf ( param ) ) ;
}
/ * *
* 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 ] ;
}
static class ShowTextInputTask implements Runnable {
/ *
* 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 ;
public ShowTextInputTask ( int x , int y , int w , int h ) {
this . x = x ;
this . y = y ;
this . w = w ;
this . h = h ;
}
@Override
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 ) ;
}
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean showTextInput ( int x , int y , int w , int h ) {
// Transfer the task to the main thread as a Runnable
return mSingleton . commandHandler . post ( new ShowTextInputTask ( x , y , w , h ) ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static Surface getNativeSurface ( ) {
return SDLActivity . mSurface . getNativeSurface ( ) ;
}
// Audio
/ * *
* This method is called by SDL using JNI .
* /
public static int audioInit ( 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 ) ;
2015-04-08 04:14:36 -06:00
2015-05-16 13:15:59 -06:00
Log . v ( TAG , "SDL audio: wanted " + ( isStereo ? "stereo" : "mono" ) + " " + ( is16Bit ? "16-bit" : "8-bit" ) + " " + ( sampleRate / 1000 f ) + "kHz, " + desiredFrames + " frames buffer" ) ;
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06: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 ) ;
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
if ( mAudioTrack = = null ) {
mAudioTrack = new AudioTrack ( AudioManager . STREAM_MUSIC , sampleRate ,
channelConfig , audioFormat , desiredFrames * frameSize , AudioTrack . MODE_STREAM ) ;
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06: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()
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
if ( mAudioTrack . getState ( ) ! = AudioTrack . STATE_INITIALIZED ) {
2015-05-16 13:15:59 -06:00
Log . e ( TAG , "Failed during initialization of Audio Track" ) ;
2015-04-20 10:22:44 -06:00
mAudioTrack = null ;
return - 1 ;
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
mAudioTrack . play ( ) ;
}
2015-04-08 04:14:36 -06:00
2015-05-16 13:15:59 -06:00
Log . v ( TAG , "SDL audio: got " + ( ( mAudioTrack . getChannelCount ( ) > = 2 ) ? "stereo" : "mono" ) + " " + ( ( mAudioTrack . getAudioFormat ( ) = = AudioFormat . ENCODING_PCM_16BIT ) ? "16-bit" : "8-bit" ) + " " + ( mAudioTrack . getSampleRate ( ) / 1000 f ) + "kHz, " + desiredFrames + " frames buffer" ) ;
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
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 {
2015-05-16 13:15:59 -06:00
Log . w ( TAG , "SDL audio: error return from write(short)" ) ;
2015-04-20 10:22:44 -06:00
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 {
2015-05-16 13:15:59 -06:00
Log . w ( TAG , "SDL audio: error return from write(byte)" ) ;
2015-04-20 10:22:44 -06:00
return ;
}
}
}
/ * *
* This method is called by SDL using JNI .
* /
public static void audioQuit ( ) {
if ( mAudioTrack ! = null ) {
mAudioTrack . stop ( ) ;
mAudioTrack = null ;
}
}
// Input
/ * *
* This method is called by SDL using JNI .
* @return an array which may be empty but is never null .
* /
public static int [ ] inputGetInputDeviceIds ( int sources ) {
int [ ] ids = InputDevice . getDeviceIds ( ) ;
int [ ] filtered = new int [ ids . length ] ;
int used = 0 ;
for ( int i = 0 ; i < ids . length ; + + i ) {
InputDevice device = InputDevice . getDevice ( ids [ i ] ) ;
if ( ( device ! = null ) & & ( ( device . getSources ( ) & sources ) ! = 0 ) ) {
filtered [ used + + ] = device . getId ( ) ;
}
}
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 ( ) ;
}
}
// APK extension files support
/** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
private Object expansionFile ;
/** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
private Method expansionFileMethod ;
/ * *
* This method is called by SDL using JNI .
* /
public InputStream openAPKExtensionInputStream ( String fileName ) throws IOException {
// Get a ZipResourceFile representing a merger of both the main and patch files
if ( expansionFile = = null ) {
Integer mainVersion = Integer . valueOf ( nativeGetHint ( "SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION" ) ) ;
Integer patchVersion = Integer . valueOf ( nativeGetHint ( "SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION" ) ) ;
try {
// To avoid direct dependency on Google APK extension library that is
// 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 ) ;
expansionFileMethod = expansionFile . getClass ( )
. getMethod ( "getInputStream" , String . class ) ;
} catch ( Exception ex ) {
ex . printStackTrace ( ) ;
expansionFile = null ;
expansionFileMethod = null ;
}
}
// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream ;
try {
fileStream = ( InputStream ) expansionFileMethod . invoke ( expansionFile , fileName ) ;
} catch ( Exception ex ) {
ex . printStackTrace ( ) ;
fileStream = null ;
}
if ( fileStream = = null ) {
throw new IOException ( ) ;
}
return fileStream ;
}
// Messagebox
/** Result of current messagebox. Also used for blocking the calling thread. */
protected final int [ ] messageboxSelection = new int [ 1 ] ;
/** Id of current dialog. */
protected int dialogs = 0 ;
/ * *
* This method is called by SDL using JNI .
* Shows the messagebox from UI thread and block calling thread .
* buttonFlags , buttonIds and buttonTexts must have same length .
* @param buttonFlags array containing flags for every button .
* @param buttonIds array containing id for every button .
* @param buttonTexts array containing text for every button .
* @param colors null for default or array of length 5 containing colors .
* @return button id or - 1.
* /
public int messageboxShowMessageBox (
final int flags ,
final String title ,
final String message ,
final int [ ] buttonFlags ,
final int [ ] buttonIds ,
final String [ ] buttonTexts ,
final int [ ] colors ) {
messageboxSelection [ 0 ] = - 1 ;
// sanity checks
if ( ( buttonFlags . length ! = buttonIds . length ) & & ( buttonIds . length ! = buttonTexts . length ) ) {
return - 1 ; // implementation broken
}
// collect arguments for Dialog
final Bundle args = new Bundle ( ) ;
args . putInt ( "flags" , flags ) ;
args . putString ( "title" , title ) ;
args . putString ( "message" , message ) ;
args . putIntArray ( "buttonFlags" , buttonFlags ) ;
args . putIntArray ( "buttonIds" , buttonIds ) ;
args . putStringArray ( "buttonTexts" , buttonTexts ) ;
args . putIntArray ( "colors" , colors ) ;
// trigger Dialog creation on UI thread
runOnUiThread ( new Runnable ( ) {
@Override
public void run ( ) {
showDialog ( dialogs + + , args ) ;
}
} ) ;
// block the calling thread
synchronized ( messageboxSelection ) {
try {
messageboxSelection . wait ( ) ;
} catch ( InterruptedException ex ) {
ex . printStackTrace ( ) ;
return - 1 ;
}
}
// return selected value
return messageboxSelection [ 0 ] ;
}
@Override
protected Dialog onCreateDialog ( int ignore , Bundle args ) {
// TODO set values from "flags" to messagebox dialog
// get colors
int [ ] colors = args . getIntArray ( "colors" ) ;
int backgroundColor ;
int textColor ;
int buttonBorderColor ;
int buttonBackgroundColor ;
int buttonSelectedColor ;
if ( colors ! = null ) {
int i = - 1 ;
backgroundColor = colors [ + + i ] ;
textColor = colors [ + + i ] ;
buttonBorderColor = colors [ + + i ] ;
buttonBackgroundColor = colors [ + + i ] ;
buttonSelectedColor = colors [ + + i ] ;
} else {
backgroundColor = Color . TRANSPARENT ;
textColor = Color . TRANSPARENT ;
buttonBorderColor = Color . TRANSPARENT ;
buttonBackgroundColor = Color . TRANSPARENT ;
buttonSelectedColor = Color . TRANSPARENT ;
}
// create dialog with title and a listener to wake up calling thread
final Dialog dialog = new Dialog ( this ) ;
dialog . setTitle ( args . getString ( "title" ) ) ;
dialog . setCancelable ( false ) ;
dialog . setOnDismissListener ( new DialogInterface . OnDismissListener ( ) {
@Override
public void onDismiss ( DialogInterface unused ) {
synchronized ( messageboxSelection ) {
messageboxSelection . notify ( ) ;
}
}
} ) ;
// create text
TextView message = new TextView ( this ) ;
message . setGravity ( Gravity . CENTER ) ;
message . setText ( args . getString ( "message" ) ) ;
if ( textColor ! = Color . TRANSPARENT ) {
message . setTextColor ( textColor ) ;
}
// create buttons
int [ ] buttonFlags = args . getIntArray ( "buttonFlags" ) ;
int [ ] buttonIds = args . getIntArray ( "buttonIds" ) ;
String [ ] buttonTexts = args . getStringArray ( "buttonTexts" ) ;
final SparseArray < Button > mapping = new SparseArray < Button > ( ) ;
LinearLayout buttons = new LinearLayout ( this ) ;
buttons . setOrientation ( LinearLayout . HORIZONTAL ) ;
buttons . setGravity ( Gravity . CENTER ) ;
for ( int i = 0 ; i < buttonTexts . length ; + + i ) {
Button button = new Button ( this ) ;
final int id = buttonIds [ i ] ;
button . setOnClickListener ( new View . OnClickListener ( ) {
@Override
public void onClick ( View v ) {
messageboxSelection [ 0 ] = id ;
dialog . dismiss ( ) ;
}
} ) ;
if ( buttonFlags [ i ] ! = 0 ) {
// see SDL_messagebox.h
if ( ( buttonFlags [ i ] & 0x00000001 ) ! = 0 ) {
mapping . put ( KeyEvent . KEYCODE_ENTER , button ) ;
}
if ( ( buttonFlags [ i ] & 0x00000002 ) ! = 0 ) {
mapping . put ( 111 , button ) ; /* API 11: KeyEvent.KEYCODE_ESCAPE */
}
}
button . setText ( buttonTexts [ i ] ) ;
if ( textColor ! = Color . TRANSPARENT ) {
button . setTextColor ( textColor ) ;
}
if ( buttonBorderColor ! = Color . TRANSPARENT ) {
// TODO set color for border of messagebox button
}
if ( buttonBackgroundColor ! = Color . TRANSPARENT ) {
Drawable drawable = button . getBackground ( ) ;
if ( drawable = = null ) {
// setting the color this way removes the style
button . setBackgroundColor ( buttonBackgroundColor ) ;
} else {
// setting the color this way keeps the style (gradient, padding, etc.)
drawable . setColorFilter ( buttonBackgroundColor , PorterDuff . Mode . MULTIPLY ) ;
}
}
if ( buttonSelectedColor ! = Color . TRANSPARENT ) {
// TODO set color for selected messagebox button
}
buttons . addView ( button ) ;
}
// create content
LinearLayout content = new LinearLayout ( this ) ;
content . setOrientation ( LinearLayout . VERTICAL ) ;
content . addView ( message ) ;
content . addView ( buttons ) ;
if ( backgroundColor ! = Color . TRANSPARENT ) {
content . setBackgroundColor ( backgroundColor ) ;
}
// add content to dialog and return
dialog . setContentView ( content ) ;
dialog . setOnKeyListener ( new Dialog . OnKeyListener ( ) {
@Override
public boolean onKey ( DialogInterface d , int keyCode , KeyEvent event ) {
Button button = mapping . get ( keyCode ) ;
if ( button ! = null ) {
if ( event . getAction ( ) = = KeyEvent . ACTION_UP ) {
button . performClick ( ) ;
}
return true ; // also for ignored actions
}
return false ;
}
} ) ;
return dialog ;
}
}
/ * *
Simple nativeInit ( ) runnable
* /
class SDLMain implements Runnable {
@Override
public void run ( ) {
// Runs SDL_main()
SDLActivity . nativeInit ( SDLActivity . mSingleton . getArguments ( ) ) ;
//Log.v("SDL", "SDL thread terminated");
}
}
/ * *
SDLSurface . This is what we draw on , so we need to know when it ' s created
2015-04-08 04:14:36 -06:00
in order to do anything useful .
2015-04-20 10:22:44 -06:00
Because of this , that ' s where we set up the SDL thread
* /
2015-04-08 04:14:36 -06:00
class SDLSurface extends SurfaceView implements SurfaceHolder . Callback ,
2015-04-20 10:22:44 -06:00
View . OnKeyListener , View . OnTouchListener , SensorEventListener {
// Sensors
protected static SensorManager mSensorManager ;
protected static Display mDisplay ;
// Keep track of the surface size to normalize touch events
protected static float mWidth , mHeight ;
2015-04-08 04:14:36 -06:00
// Startup
2015-04-20 10:22:44 -06:00
public SDLSurface ( Context context ) {
super ( context ) ;
2015-04-08 04:14:36 -06:00
getHolder ( ) . addCallback ( this ) ;
2015-04-20 10:22:44 -06:00
setFocusable ( true ) ;
setFocusableInTouchMode ( true ) ;
requestFocus ( ) ;
2015-04-08 04:14:36 -06:00
setOnKeyListener ( this ) ;
setOnTouchListener ( this ) ;
2015-04-20 10:22:44 -06:00
mDisplay = ( ( WindowManager ) context . getSystemService ( Context . WINDOW_SERVICE ) ) . getDefaultDisplay ( ) ;
mSensorManager = ( SensorManager ) context . getSystemService ( Context . SENSOR_SERVICE ) ;
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
if ( Build . VERSION . SDK_INT > = 12 ) {
setOnGenericMotionListener ( new SDLGenericMotionListener_API12 ( ) ) ;
}
// Some arbitrary defaults to avoid a potential division by zero
mWidth = 1.0f ;
mHeight = 1.0f ;
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
public void handleResume ( ) {
setFocusable ( true ) ;
setFocusableInTouchMode ( true ) ;
requestFocus ( ) ;
setOnKeyListener ( this ) ;
setOnTouchListener ( this ) ;
enableSensor ( Sensor . TYPE_ACCELEROMETER , true ) ;
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
public Surface getNativeSurface ( ) {
return getHolder ( ) . getSurface ( ) ;
}
// Called when we have a valid drawing surface
@Override
public void surfaceCreated ( SurfaceHolder holder ) {
Log . v ( "SDL" , "surfaceCreated()" ) ;
holder . setType ( SurfaceHolder . SURFACE_TYPE_GPU ) ;
}
// Called when we lose the surface
@Override
public void surfaceDestroyed ( SurfaceHolder holder ) {
Log . v ( "SDL" , "surfaceDestroyed()" ) ;
// Call this *before* setting mIsSurfaceReady to 'false'
SDLActivity . handlePause ( ) ;
SDLActivity . mIsSurfaceReady = false ;
SDLActivity . onNativeSurfaceDestroyed ( ) ;
}
// Called when the surface is resized
@Override
public void surfaceChanged ( SurfaceHolder holder ,
int format , int width , int height ) {
Log . v ( "SDL" , "surfaceChanged()" ) ;
int sdlFormat = 0x15151002 ; // SDL_PIXELFORMAT_RGB565 by default
switch ( format ) {
case PixelFormat . A_8 :
Log . v ( "SDL" , "pixel format A_8" ) ;
break ;
case PixelFormat . LA_88 :
Log . v ( "SDL" , "pixel format LA_88" ) ;
break ;
case PixelFormat . L_8 :
Log . v ( "SDL" , "pixel format L_8" ) ;
break ;
case PixelFormat . RGBA_4444 :
Log . v ( "SDL" , "pixel format RGBA_4444" ) ;
sdlFormat = 0x15421002 ; // SDL_PIXELFORMAT_RGBA4444
break ;
case PixelFormat . RGBA_5551 :
Log . v ( "SDL" , "pixel format RGBA_5551" ) ;
sdlFormat = 0x15441002 ; // SDL_PIXELFORMAT_RGBA5551
break ;
case PixelFormat . RGBA_8888 :
Log . v ( "SDL" , "pixel format RGBA_8888" ) ;
sdlFormat = 0x16462004 ; // SDL_PIXELFORMAT_RGBA8888
break ;
case PixelFormat . RGBX_8888 :
Log . v ( "SDL" , "pixel format RGBX_8888" ) ;
sdlFormat = 0x16261804 ; // SDL_PIXELFORMAT_RGBX8888
break ;
case PixelFormat . RGB_332 :
Log . v ( "SDL" , "pixel format RGB_332" ) ;
sdlFormat = 0x14110801 ; // SDL_PIXELFORMAT_RGB332
break ;
case PixelFormat . RGB_565 :
Log . v ( "SDL" , "pixel format RGB_565" ) ;
sdlFormat = 0x15151002 ; // SDL_PIXELFORMAT_RGB565
break ;
case PixelFormat . RGB_888 :
Log . v ( "SDL" , "pixel format RGB_888" ) ;
// Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
sdlFormat = 0x16161804 ; // SDL_PIXELFORMAT_RGB888
break ;
default :
Log . v ( "SDL" , "pixel format unknown " + format ) ;
break ;
}
mWidth = width ;
mHeight = height ;
SDLActivity . onNativeResize ( width , height , sdlFormat , mDisplay . getRefreshRate ( ) ) ;
Log . v ( "SDL" , "Window size:" + width + "x" + height ) ;
// Set mIsSurfaceReady to 'true' *before* making a call to handleResume
SDLActivity . mIsSurfaceReady = true ;
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 ( ) ;
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
// 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 ) { }
2015-04-08 04:14:36 -06:00
finally {
2015-04-20 10:22:44 -06:00
// Native thread has finished
if ( ! SDLActivity . mExitCalledFromJava ) {
SDLActivity . handleNativeExit ( ) ;
}
}
}
} , "SDLThreadListener" ) ;
SDLActivity . mSDLThread . start ( ) ;
}
}
// unused
@Override
public void onDraw ( Canvas canvas ) { }
// Key events
@Override
public boolean onKey ( View v , int keyCode , KeyEvent event ) {
// Dispatch the different events depending on where they come from
// Some SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
// So, we try to process them as DPAD or GAMEPAD events first, if that fails we try them as KEYBOARD
if ( ( event . getSource ( ) & InputDevice . SOURCE_GAMEPAD ) ! = 0 | |
( event . getSource ( ) & InputDevice . SOURCE_DPAD ) ! = 0 ) {
if ( event . getAction ( ) = = KeyEvent . ACTION_DOWN ) {
if ( SDLActivity . onNativePadDown ( event . getDeviceId ( ) , keyCode ) = = 0 ) {
return true ;
}
} else if ( event . getAction ( ) = = KeyEvent . ACTION_UP ) {
if ( SDLActivity . onNativePadUp ( event . getDeviceId ( ) , keyCode ) = = 0 ) {
return true ;
}
}
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
if ( ( event . getSource ( ) & InputDevice . SOURCE_KEYBOARD ) ! = 0 ) {
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 ;
}
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
return false ;
}
// Touch events
@Override
public boolean onTouch ( View v , MotionEvent event ) {
/* Ref: http://developer.android.com/training/gestures/multi.html */
final int touchDevId = event . getDeviceId ( ) ;
final int pointerCount = event . getPointerCount ( ) ;
int action = event . getActionMasked ( ) ;
int pointerFingerId ;
int mouseButton ;
int i = - 1 ;
float x , y , p ;
// !!! FIXME: dump this SDK check after 2.0.4 ships and require API14.
if ( event . getSource ( ) = = InputDevice . SOURCE_MOUSE & & SDLActivity . mSeparateMouseAndTouch ) {
if ( Build . VERSION . SDK_INT < 14 ) {
mouseButton = 1 ; // For Android==12 all mouse buttons are the left button
} else {
try {
mouseButton = ( Integer ) event . getClass ( ) . getMethod ( "getButtonState" ) . invoke ( event ) ;
} catch ( Exception e ) {
mouseButton = 1 ; // oh well.
}
}
SDLActivity . onNativeMouse ( mouseButton , action , event . getX ( 0 ) , event . getY ( 0 ) ) ;
} else {
switch ( action ) {
case MotionEvent . ACTION_MOVE :
for ( i = 0 ; i < pointerCount ; i + + ) {
pointerFingerId = event . getPointerId ( i ) ;
x = event . getX ( i ) / mWidth ;
y = event . getY ( i ) / mHeight ;
p = event . getPressure ( i ) ;
SDLActivity . onNativeTouch ( touchDevId , pointerFingerId , action , x , y , p ) ;
}
break ;
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
case MotionEvent . ACTION_UP :
case MotionEvent . ACTION_DOWN :
// Primary pointer up/down, the index is always zero
i = 0 ;
case MotionEvent . ACTION_POINTER_UP :
case MotionEvent . ACTION_POINTER_DOWN :
// Non primary pointer up/down
if ( i = = - 1 ) {
i = event . getActionIndex ( ) ;
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
pointerFingerId = event . getPointerId ( i ) ;
x = event . getX ( i ) / mWidth ;
y = event . getY ( i ) / mHeight ;
p = event . getPressure ( i ) ;
SDLActivity . onNativeTouch ( touchDevId , pointerFingerId , action , x , y , p ) ;
break ;
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
case MotionEvent . ACTION_CANCEL :
for ( i = 0 ; i < pointerCount ; i + + ) {
pointerFingerId = event . getPointerId ( i ) ;
x = event . getX ( i ) / mWidth ;
y = event . getY ( i ) / mHeight ;
p = event . getPressure ( i ) ;
SDLActivity . onNativeTouch ( touchDevId , pointerFingerId , MotionEvent . ACTION_UP , x , y , p ) ;
}
break ;
default :
break ;
}
}
return true ;
2015-04-08 04:14:36 -06:00
}
2015-04-20 10:22:44 -06:00
// Sensor events
public void enableSensor ( int sensortype , boolean enabled ) {
// TODO: This uses getDefaultSensor - what if we have >1 accels?
if ( enabled ) {
2015-04-08 04:14:36 -06:00
mSensorManager . registerListener ( this ,
mSensorManager . getDefaultSensor ( sensortype ) ,
2015-04-20 10:22:44 -06:00
SensorManager . SENSOR_DELAY_GAME , null ) ;
} else {
2015-04-08 04:14:36 -06:00
mSensorManager . unregisterListener ( this ,
2015-04-20 10:22:44 -06:00
mSensorManager . getDefaultSensor ( sensortype ) ) ;
}
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
@Override
public void onAccuracyChanged ( Sensor sensor , int accuracy ) {
// TODO
}
@Override
public void onSensorChanged ( SensorEvent event ) {
if ( event . sensor . getType ( ) = = Sensor . TYPE_ACCELEROMETER ) {
float x , y ;
switch ( mDisplay . getRotation ( ) ) {
case Surface . ROTATION_90 :
x = - event . values [ 1 ] ;
y = event . values [ 0 ] ;
break ;
case Surface . ROTATION_270 :
x = event . values [ 1 ] ;
y = - event . values [ 0 ] ;
break ;
case Surface . ROTATION_180 :
x = - event . values [ 1 ] ;
y = - event . values [ 0 ] ;
break ;
default :
x = event . values [ 0 ] ;
y = event . values [ 1 ] ;
break ;
}
SDLActivity . onNativeAccel ( - x / SensorManager . GRAVITY_EARTH ,
y / SensorManager . GRAVITY_EARTH ,
event . values [ 2 ] / SensorManager . GRAVITY_EARTH - 1 ) ;
}
2015-04-08 04:14:36 -06:00
}
2015-04-20 10:22:44 -06: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 ;
}
@Override
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 ;
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06: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 ) ;
}
@Override
public InputConnection onCreateInputConnection ( EditorInfo outAttrs ) {
ic = new SDLInputConnection ( this , true ) ;
outAttrs . imeOptions = EditorInfo . IME_FLAG_NO_EXTRACT_UI
| 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */ ;
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 ) {
if ( event . isPrintingKey ( ) ) {
commitText ( String . valueOf ( ( char ) event . getUnicodeChar ( ) ) , 1 ) ;
}
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 ) ;
@Override
2015-04-08 04:14:36 -06:00
public boolean deleteSurroundingText ( int beforeLength , int afterLength ) {
2015-04-20 10:22:44 -06:00
// 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 ) ) ;
}
return super . deleteSurroundingText ( beforeLength , afterLength ) ;
}
}
/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */
class SDLJoystickHandler {
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
/ * *
* 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 ArrayList < InputDevice . MotionRange > axes ;
public ArrayList < InputDevice . MotionRange > hats ;
}
static class RangeComparator implements Comparator < InputDevice . MotionRange > {
@Override
public int compare ( InputDevice . MotionRange arg0 , InputDevice . MotionRange arg1 ) {
return arg0 . getAxis ( ) - arg1 . getAxis ( ) ;
}
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
private ArrayList < SDLJoystick > mJoysticks ;
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
public SDLJoystickHandler_API12 ( ) {
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
mJoysticks = new ArrayList < SDLJoystick > ( ) ;
}
@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
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
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 ( ( joystickDevice . getSources ( ) & InputDevice . SOURCE_CLASS_JOYSTICK ) ! = 0 ) {
joystick . device_id = deviceIds [ i ] ;
joystick . name = joystickDevice . getName ( ) ;
joystick . axes = new ArrayList < InputDevice . MotionRange > ( ) ;
joystick . hats = new ArrayList < InputDevice . MotionRange > ( ) ;
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
List < InputDevice . MotionRange > 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 ) ;
}
}
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
mJoysticks . add ( joystick ) ;
2015-04-08 04:14:36 -06:00
SDLActivity . nativeAddJoystick ( joystick . device_id , joystick . name , 0 , - 1 ,
2015-04-20 10:22:44 -06:00
joystick . axes . size ( ) , joystick . hats . size ( ) / 2 , 0 ) ;
}
}
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
/* Check removed devices */
ArrayList < Integer > removedDevices = new ArrayList < Integer > ( ) ;
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 ) ) ;
}
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
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 ;
}
}
2015-04-08 04:14:36 -06:00
}
2015-04-20 10:22:44 -06:00
}
2015-04-08 04:14:36 -06:00
2015-04-20 10:22:44 -06:00
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 ;
2015-04-08 04:14:36 -06:00
}
@Override
2015-04-20 10:22:44 -06:00
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 ) ;
2015-04-08 04:14:36 -06:00
}
2015-04-20 10:22:44 -06:00
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 ;
2015-04-08 04:14:36 -06:00
}
2015-04-20 10:22:44 -06:00
}
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 mouseButton ;
int action ;
switch ( event . getSource ( ) ) {
case InputDevice . SOURCE_JOYSTICK :
case InputDevice . SOURCE_GAMEPAD :
case InputDevice . SOURCE_DPAD :
SDLActivity . handleJoystickMotionEvent ( event ) ;
return true ;
case InputDevice . SOURCE_MOUSE :
action = event . getActionMasked ( ) ;
switch ( action ) {
case MotionEvent . ACTION_SCROLL :
x = event . getAxisValue ( MotionEvent . AXIS_HSCROLL , 0 ) ;
y = event . getAxisValue ( MotionEvent . AXIS_VSCROLL , 0 ) ;
SDLActivity . onNativeMouse ( 0 , action , x , y ) ;
return true ;
case MotionEvent . ACTION_HOVER_MOVE :
x = event . getX ( 0 ) ;
y = event . getY ( 0 ) ;
SDLActivity . onNativeMouse ( 0 , action , x , y ) ;
return true ;
default :
break ;
}
default :
break ;
}
// Event was not managed
return false ;
}
}