diff --git a/changeAppSettings.sh b/changeAppSettings.sh index 5669a6773..4ff58a88f 100755 --- a/changeAppSettings.sh +++ b/changeAppSettings.sh @@ -68,11 +68,16 @@ if [ "$AppUsesJoystick" != "y" ]; then AppUsesSecondJoystick=n fi +if [ "$LibSdlVersion" = "2.0" ]; then + echo Patch of settings menu not supported in SDL 2.0 +else MenuOptionsAvailable= for FF in Menu MenuMisc MenuMouse MenuKeyboard ; do MenuOptionsAvailable1=`grep 'extends Menu' $JAVA_SRC_PATH/Settings$FF.java | sed "s/.* class \(.*\) extends .*/Settings$FF.\1/" | tr '\n' ' '` MenuOptionsAvailable="$MenuOptionsAvailable $MenuOptionsAvailable1" done +fi + FirstStartMenuOptionsDefault='new SettingsMenuMisc.ShowReadme(), (AppUsesMouse \&\& \! ForceRelativeMouseMode \? new SettingsMenuMouse.DisplaySizeConfig(true) : new SettingsMenu.DummyMenu()), new SettingsMenuMisc.OptionalDownloadConfig(true), new SettingsMenuMisc.GyroscopeCalibration()' @@ -809,26 +814,26 @@ fi rm -rf project/src mkdir -p project/src cd $JAVA_SRC_PATH + for F in *.java; do echo '// DO NOT EDIT THIS FILE - it is automatically generated, ALL YOUR CHANGES WILL BE OVERWRITTEN, edit the file under $JAVA_SRC_PATH dir' | cat - $F > ../src/$F done -if [ -e ../jni/application/src/java.diff ]; then patch -d ../src --no-backup-if-mismatch < ../jni/application/src/java.diff || exit 1 ; fi -if [ -e ../jni/application/src/java.patch ]; then patch -d ../src --no-backup-if-mismatch < ../jni/application/src/java.patch || exit 1 ; fi -if ls ../jni/application/src/*.java > /dev/null 2>&1; then cp -f ../jni/application/src/*.java ../src ; fi +if [ "$LibSdlVersion" = "2.0" ] ; then + echo Patching java with SDL 2.0 not supported +else + if [ -e ../jni/application/src/java.diff ]; then patch -d ../src --no-backup-if-mismatch < ../jni/application/src/java.diff || exit 1 ; fi + if [ -e ../jni/application/src/java.patch ]; then patch -d ../src --no-backup-if-mismatch < ../jni/application/src/java.patch || exit 1 ; fi + if ls ../jni/application/src/*.java > /dev/null 2>&1; then cp -f ../jni/application/src/*.java ../src ; fi -for F in ../src/*.java; do + for F in ../src/*.java; do echo Patching $F $SEDI "s/^package .*;/package $AppFullName;/" $F -done + done +fi cd ../.. -# In case we use SDL2 let simlink the SDLActivity source file -if [ "$LibSdlVersion" = "2.0" ] ; then - ln -s ../jni/sdl-2.0/android-project/src/org/libsdl/app/SDLActivity.java project/src/SDLActivity.java -fi - echo Patching project/AndroidManifest.xml cat project/AndroidManifestTemplate.xml | \ @@ -937,6 +942,9 @@ else fi +if [ "$LibSdlVersion" = "2.0" ] ; then + echo Patching java files not supported in SDL 2.0 yet. +else echo Patching project/src/Globals.java $SEDI "s/public static String ApplicationName = .*;/public static String ApplicationName = \"$AppShortName\";/" project/src/Globals.java $SEDI "s/public static final boolean Using_SDL_1_3 = .*;/public static final boolean Using_SDL_1_3 = $UsingSdl13;/" project/src/Globals.java @@ -1002,6 +1010,7 @@ $SEDI "s/public static String AdmobBannerSize = .*/public static String AdmobBan $SEDI "s%public static String GooglePlayGameServicesId = .*%public static String GooglePlayGameServicesId = \"$GooglePlayGameServicesId\";%" project/src/Globals.java $SEDI "s/public static String AppLibraries.*/public static String AppLibraries[] = { $LibrariesToLoad };/" project/src/Globals.java $SEDI "s/public static String AppMainLibraries.*/public static String AppMainLibraries[] = { $MainLibrariesToLoad };/" project/src/Globals.java +fi # TODO: We should not build png, jpeg if SDL2_image is used @@ -1050,13 +1059,16 @@ echo Patching strings.xml rm -rf project/res/values*/strings.xml cd $JAVA_SRC_PATH/translations for F in */strings.xml; do - mkdir -p ../../res/`dirname $F` - cat $F | \ - sed "s^[<]string name=\"app_name\"[>].*^$AppName^" > \ - ../../res/$F + mkdir -p ../../res/`dirname $F` + cat $F | \ + sed "s^[<]string name=\"app_name\"[>].*^$AppName^" > \ + ../../res/$F done + cd ../../.. + + SDK_DIR=`grep '^sdk.dir' project/local.properties | sed 's/.*=//'` [ -z "$SDK_DIR" ] && SDK_DIR=`which android | sed 's@/tools/android$@@'` mkdir -p project/libs diff --git a/project/javaSDL2/Accelerometer.java b/project/javaSDL2/Accelerometer.java deleted file mode 100644 index 978020850..000000000 --- a/project/javaSDL2/Accelerometer.java +++ /dev/null @@ -1,129 +0,0 @@ -/* -Simple DirectMedia Layer -Java source code (C) 2009-2012 Sergii Pylypenko - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. -*/ - -package net.sourceforge.clonekeenplus; - -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.view.MotionEvent; -import android.view.KeyEvent; -import android.view.Window; -import android.view.WindowManager; -import android.os.Vibrator; -import android.hardware.SensorManager; -import android.hardware.SensorEventListener; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.util.Log; -import android.widget.TextView; - - -class AccelerometerReader implements SensorEventListener -{ - - private SensorManager _manager = null; - public boolean openedBySDL = false; - public static final GyroscopeListener gyro = new GyroscopeListener(); - - public AccelerometerReader(Activity context) - { - _manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - } - - public synchronized void stop() - { - if( _manager != null ) - { - Log.i("SDL", "libSDL: stopping accelerometer/gyroscope"); - _manager.unregisterListener(this); - _manager.unregisterListener(gyro); - } - } - - public synchronized void start() - { - if( (Globals.UseAccelerometerAsArrowKeys || Globals.AppUsesAccelerometer) && - _manager != null && _manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null ) - { - Log.i("SDL", "libSDL: starting accelerometer"); - _manager.registerListener(this, _manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME); - } - if( Globals.AppUsesGyroscope && _manager != null && _manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null ) - { - Log.i("SDL", "libSDL: starting gyroscope"); - _manager.registerListener(gyro, _manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_GAME); - } - } - - public void onSensorChanged(SensorEvent event) - { - if( Globals.HorizontalOrientation ) - nativeAccelerometer(event.values[1], -event.values[0], event.values[2]); - else - nativeAccelerometer(event.values[0], event.values[1], event.values[2]); // TODO: not tested! - } - public void onAccuracyChanged(Sensor s, int a) - { - } - - static class GyroscopeListener implements SensorEventListener - { - public float x1, x2, xc, y1, y2, yc, z1, z2, zc; - public GyroscopeListener() - { - } - public void onSensorChanged(SensorEvent event) - { - // TODO: vertical orientation - //if( Globals.HorizontalOrientation ) - if( event.values[0] < x1 || event.values[0] > x2 || - event.values[1] < y1 || event.values[1] > y2 || - event.values[2] < z1 || event.values[2] > z2 ) - nativeGyroscope(event.values[0] - xc, event.values[1] - yc, event.values[2] - zc); - } - public void onAccuracyChanged(Sensor s, int a) - { - } - public boolean available(Activity context) - { - SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - return ( manager != null && manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null ); - } - public void registerListener(Activity context, SensorEventListener l) - { - SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - if ( manager == null && manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) == null ) - return; - manager.registerListener(l, manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_GAME); - } - public void unregisterListener(Activity context,SensorEventListener l) - { - SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - if ( manager == null ) - return; - manager.unregisterListener(l); - } - } - - private static native void nativeAccelerometer(float accX, float accY, float accZ); - private static native void nativeGyroscope(float X, float Y, float Z); -} diff --git a/project/javaSDL2/Advertisement.java b/project/javaSDL2/Advertisement.java deleted file mode 100644 index 43b007fd7..000000000 --- a/project/javaSDL2/Advertisement.java +++ /dev/null @@ -1,50 +0,0 @@ -/* -Simple DirectMedia Layer -Java source code (C) 2009-2012 Sergii Pylypenko - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. -*/ - -package net.sourceforge.clonekeenplus; - -import android.app.Activity; -import android.content.Context; -import android.view.MotionEvent; -import android.view.KeyEvent; -import android.view.Window; -import android.view.WindowManager; -import android.widget.TextView; -import android.view.View; - -class Advertisement -{ - MainActivity parent; - - public Advertisement(MainActivity p) - { - parent = p; - } - - public View getView() - { - return null; - } - - public void requestNewAd() - { - } -} diff --git a/project/javaSDL2/Audio.java b/project/javaSDL2/Audio.java deleted file mode 100644 index 080cbf97f..000000000 --- a/project/javaSDL2/Audio.java +++ /dev/null @@ -1,307 +0,0 @@ -/* -Simple DirectMedia Layer -Java source code (C) 2009-2012 Sergii Pylypenko - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. -*/ - -package net.sourceforge.clonekeenplus; - - -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.view.MotionEvent; -import android.view.KeyEvent; -import android.view.Window; -import android.view.WindowManager; -import android.media.AudioTrack; -import android.media.AudioManager; -import android.media.AudioFormat; -import android.media.AudioRecord; -import android.media.MediaRecorder.AudioSource; -import java.io.*; -import android.util.Log; -import java.util.concurrent.Semaphore; - - - -class AudioThread -{ - - private MainActivity mParent; - private AudioTrack mAudio; - private byte[] mAudioBuffer; - private int mVirtualBufSize; - - public AudioThread(MainActivity parent) - { - mParent = parent; - mAudio = null; - mAudioBuffer = null; - //nativeAudioInitJavaCallbacks(); - } - - public int fillBuffer() - { - if( mParent.isPaused() ) - { - try{ - Thread.sleep(500); - } catch (InterruptedException e) {} - } - else - { - //if( Globals.AudioBufferConfig == 0 ) // Gives too much spam to logcat, makes things worse - // mAudio.flush(); - - mAudio.write( mAudioBuffer, 0, mVirtualBufSize ); - } - - return 1; - } - - public int initAudio(int rate, int channels, int encoding, int bufSize) - { - if( mAudio == null ) - { - channels = ( channels == 1 ) ? AudioFormat.CHANNEL_CONFIGURATION_MONO : - AudioFormat.CHANNEL_CONFIGURATION_STEREO; - encoding = ( encoding == 1 ) ? AudioFormat.ENCODING_PCM_16BIT : - AudioFormat.ENCODING_PCM_8BIT; - - mVirtualBufSize = bufSize; - - if( AudioTrack.getMinBufferSize( rate, channels, encoding ) > bufSize ) - bufSize = AudioTrack.getMinBufferSize( rate, channels, encoding ); - - if(Globals.AudioBufferConfig != 0) { // application's choice - use minimal buffer - bufSize = (int)((float)bufSize * (((float)(Globals.AudioBufferConfig - 1) * 2.5f) + 1.0f)); - mVirtualBufSize = bufSize; - } - mAudioBuffer = new byte[bufSize]; - - mAudio = new AudioTrack(AudioManager.STREAM_MUSIC, - rate, - channels, - encoding, - bufSize, - AudioTrack.MODE_STREAM ); - mAudio.play(); - } - return mVirtualBufSize; - } - - public byte[] getBuffer() - { - return mAudioBuffer; - } - - public int deinitAudio() - { - if( mAudio != null ) - { - mAudio.stop(); - mAudio.release(); - mAudio = null; - } - mAudioBuffer = null; - return 1; - } - - public int initAudioThread() - { - // Make audio thread priority higher so audio thread won't get underrun - Thread.currentThread().setPriority(Thread.MAX_PRIORITY); - return 1; - } - - public int pauseAudioPlayback() - { - if( mAudio != null ) - { - mAudio.pause(); - } - if( mRecordThread != null ) - { - mRecordThread.pauseRecording(); - } - return 1; - } - - public int resumeAudioPlayback() - { - if( mAudio != null ) - { - mAudio.play(); - } - if( mRecordThread != null ) - { - mRecordThread.resumeRecording(); - } - return 1; - } - - private native int nativeAudioInitJavaCallbacks(); - - // ----- Audio recording ----- - - private RecordingThread mRecordThread = null; - private AudioRecord mRecorder = null; - private int mRecorderBufferSize = 0; - - private byte[] startRecording(int rate, int channels, int encoding, int bufsize) - { - if( mRecordThread == null ) - { - mRecordThread = new RecordingThread(); - mRecordThread.start(); - } - if( !mRecordThread.isStopped() ) - { - Log.i("SDL", "SDL: error: application already opened audio recording device"); - return null; - } - - mRecordThread.init(bufsize); - - int channelConfig = ( channels == 1 ) ? AudioFormat.CHANNEL_IN_MONO : - AudioFormat.CHANNEL_IN_STEREO; - int encodingConfig = ( encoding == 1 ) ? AudioFormat.ENCODING_PCM_16BIT : - AudioFormat.ENCODING_PCM_8BIT; - - int minBufDevice = AudioRecord.getMinBufferSize(rate, channelConfig, encodingConfig); - int minBufferSize = Math.max(bufsize * 8, minBufDevice + (bufsize - (minBufDevice % bufsize))); - Log.i("SDL", "SDL: app opened recording device, rate " + rate + " channels " + channels + " sample size " + (encoding+1) + " bufsize " + bufsize + " internal bufsize " + minBufferSize); - if( mRecorder == null || mRecorder.getSampleRate() != rate || - mRecorder.getChannelCount() != channels || - mRecorder.getAudioFormat() != encodingConfig || - mRecorderBufferSize != minBufferSize ) - { - if( mRecorder != null ) - mRecorder.release(); - mRecorder = null; - try { - mRecorder = new AudioRecord(AudioSource.DEFAULT, rate, channelConfig, encodingConfig, minBufferSize); - mRecorderBufferSize = minBufferSize; - } catch (IllegalArgumentException e) { - Log.i("SDL", "SDL: error: failed to open recording device!"); - return null; - } - } - else - { - Log.i("SDL", "SDL: reusing old recording device"); - } - mRecordThread.startRecording(); - return mRecordThread.mRecordBuffer; - } - - private void stopRecording() - { - if( mRecordThread == null || mRecordThread.isStopped() ) - { - Log.i("SDL", "SDL: error: application already closed audio recording device"); - return; - } - mRecordThread.stopRecording(); - Log.i("SDL", "SDL: app closed recording device"); - } - - private class RecordingThread extends Thread - { - private boolean stopped = true; - byte[] mRecordBuffer; - private Semaphore waitStarted = new Semaphore(0); - private boolean sleep = false; - - RecordingThread() - { - super(); - } - - void init(int bufsize) - { - if( mRecordBuffer == null || mRecordBuffer.length != bufsize ) - mRecordBuffer = new byte[bufsize]; - } - - public void run() - { - while( true ) - { - waitStarted.acquireUninterruptibly(); - waitStarted.drainPermits(); - stopped = false; - sleep = false; - - while( !sleep ) - { - int got = mRecorder.read(mRecordBuffer, 0, mRecordBuffer.length); - if( got != mRecordBuffer.length ) - { - // Audio is stopped here, sleep a bit. - try{ - Thread.sleep(1000); - } catch (InterruptedException e) {} - } - else - { - //Log.i("SDL", "SDL: nativeAudioRecordCallback with len " + mRecordBuffer.length); - nativeAudioRecordCallback(); - //Log.i("SDL", "SDL: nativeAudioRecordCallback returned"); - } - } - - stopped = true; - mRecorder.stop(); - } - } - - public void startRecording() - { - mRecorder.startRecording(); - waitStarted.release(); - } - public void stopRecording() - { - sleep = true; - while( !stopped ) - { - try{ - Thread.sleep(100); - } catch (InterruptedException e) {} - } - } - public void pauseRecording() - { - if( !stopped ) - mRecorder.stop(); - } - public void resumeRecording() - { - if( !stopped ) - mRecorder.startRecording(); - } - public boolean isStopped() - { - return stopped; - } - } - - private native void nativeAudioRecordCallback(); -} diff --git a/project/javaSDL2/DataDownloader.java b/project/javaSDL2/DataDownloader.java deleted file mode 100644 index b5e22d130..000000000 --- a/project/javaSDL2/DataDownloader.java +++ /dev/null @@ -1,758 +0,0 @@ -/* -Simple DirectMedia Layer -Java source code (C) 2009-2012 Sergii Pylypenko - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. -*/ - -package net.sourceforge.clonekeenplus; - -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.view.MotionEvent; -import android.view.KeyEvent; -import android.view.Window; -import android.view.WindowManager; -import android.os.Environment; - -import android.widget.TextView; -import org.apache.http.client.methods.*; -import org.apache.http.*; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.conn.*; -import org.apache.http.conn.params.*; -import org.apache.http.conn.scheme.*; -import org.apache.http.conn.ssl.*; -import org.apache.http.impl.*; -import org.apache.http.impl.client.*; -import org.apache.http.impl.conn.SingleClientConnManager; -import java.security.cert.*; -import java.security.SecureRandom; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import java.util.zip.*; -import java.io.*; -import android.util.Log; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; - -import android.content.Context; -import android.content.res.Resources; -import java.lang.String; -import android.text.SpannedString; -import android.app.AlertDialog; -import android.content.DialogInterface; - - -class CountingInputStream extends BufferedInputStream -{ - - private long bytesReadMark = 0; - private long bytesRead = 0; - - public CountingInputStream(InputStream in, int size) { - - super(in, size); - } - - public CountingInputStream(InputStream in) { - - super(in); - } - - public long getBytesRead() { - - return bytesRead; - } - - public synchronized int read() throws IOException { - - int read = super.read(); - if (read >= 0) { - bytesRead++; - } - return read; - } - - public synchronized int read(byte[] b, int off, int len) throws IOException { - - int read = super.read(b, off, len); - if (read >= 0) { - bytesRead += read; - } - return read; - } - - public synchronized long skip(long n) throws IOException { - - long skipped = super.skip(n); - if (skipped >= 0) { - bytesRead += skipped; - } - return skipped; - } - - public synchronized void mark(int readlimit) { - - super.mark(readlimit); - bytesReadMark = bytesRead; - } - - public synchronized void reset() throws IOException { - - super.reset(); - bytesRead = bytesReadMark; - } -} - - -class DataDownloader extends Thread -{ - - public static final String DOWNLOAD_FLAG_FILENAME = "libsdl-DownloadFinished-"; - - class StatusWriter - { - private TextView Status; - private MainActivity Parent; - private SpannedString oldText = new SpannedString(""); - - public StatusWriter( TextView _Status, MainActivity _Parent ) - { - Status = _Status; - Parent = _Parent; - } - public void setParent( TextView _Status, MainActivity _Parent ) - { - synchronized(DataDownloader.this) { - Status = _Status; - Parent = _Parent; - setText( oldText.toString() ); - } - } - - public void setText(final String str) - { - class Callback implements Runnable - { - public TextView Status; - public SpannedString text; - public void run() - { - Status.setText(text); - } - } - synchronized(DataDownloader.this) { - Callback cb = new Callback(); - oldText = new SpannedString(str); - cb.text = new SpannedString(str); - cb.Status = Status; - if( Parent != null && Status != null ) - Parent.runOnUiThread(cb); - } - } - - } - public DataDownloader( MainActivity _Parent, TextView _Status ) - { - Parent = _Parent; - Status = new StatusWriter( _Status, _Parent ); - //Status.setText( "Connecting to " + Globals.DataDownloadUrl ); - outFilesDir = Globals.DataDir; - DownloadComplete = false; - this.start(); - } - - public void setStatusField(TextView _Status) - { - synchronized(this) { - Status.setParent( _Status, Parent ); - } - } - - @Override - public void run() - { - Parent.keyListener = new BackKeyListener(Parent); - String [] downloadFiles = Globals.DataDownloadUrl; - int total = 0; - int count = 0; - for( int i = 0; i < downloadFiles.length; i++ ) - { - if( downloadFiles[i].length() > 0 && - ( Globals.OptionalDataDownload.length > i && Globals.OptionalDataDownload[i] ) || - ( Globals.OptionalDataDownload.length <= i && downloadFiles[i].indexOf("!") == 0 ) ) - total += 1; - } - for( int i = 0; i < downloadFiles.length; i++ ) - { - if( downloadFiles[i].length() > 0 && - ( Globals.OptionalDataDownload.length > i && Globals.OptionalDataDownload[i] ) || - ( Globals.OptionalDataDownload.length <= i && downloadFiles[i].indexOf("!") == 0 ) ) - { - if( ! DownloadDataFile(downloadFiles[i], DOWNLOAD_FLAG_FILENAME + String.valueOf(i) + ".flag", count+1, total, i) ) - { - DownloadFailed = true; - return; - } - count += 1; - } - } - DownloadComplete = true; - Parent.keyListener = null; - initParent(); - } - - public boolean DownloadDataFile(final String DataDownloadUrl, final String DownloadFlagFileName, int downloadCount, int downloadTotal, int downloadIndex) - { - DownloadCanBeResumed = false; - Resources res = Parent.getResources(); - - String [] downloadUrls = DataDownloadUrl.split("[|]"); - if( downloadUrls.length < 2 ) - { - Log.i("SDL", "Error: download string invalid: '" + DataDownloadUrl + "', your AndroidAppSettigns.cfg is broken"); - Status.setText( res.getString(R.string.error_dl_from, DataDownloadUrl) ); - return false; - } - - boolean forceOverwrite = false; - String path = getOutFilePath(DownloadFlagFileName); - InputStream checkFile = null; - try { - checkFile = new FileInputStream( path ); - } catch( FileNotFoundException e ) { - } catch( SecurityException e ) { }; - if( checkFile != null ) - { - try { - byte b[] = new byte[ Globals.DataDownloadUrl[downloadIndex].getBytes("UTF-8").length + 1 ]; - int readed = checkFile.read(b); - String compare = ""; - if( readed > 0 ) - compare = new String( b, 0, readed, "UTF-8" ); - boolean matched = false; - //Log.i("SDL", "Read URL: '" + compare + "'"); - for( int i = 1; i < downloadUrls.length; i++ ) - { - //Log.i("SDL", "Comparing: '" + downloadUrls[i] + "'"); - if( compare.compareTo(downloadUrls[i]) == 0 ) - matched = true; - } - //Log.i("SDL", "Matched: " + String.valueOf(matched)); - if( ! matched ) - throw new IOException(); - Status.setText( res.getString(R.string.download_unneeded) ); - return true; - } catch ( IOException e ) { - forceOverwrite = true; - new File(path).delete(); - } - } - checkFile = null; - - // Create output directory (not necessary for phone storage) - Log.i("SDL", "Downloading data to: '" + outFilesDir + "'"); - try { - File outDir = new File( outFilesDir ); - if( !(outDir.exists() && outDir.isDirectory()) ) - outDir.mkdirs(); - OutputStream out = new FileOutputStream( getOutFilePath(".nomedia") ); - out.flush(); - out.close(); - } - catch( SecurityException e ) {} - catch( FileNotFoundException e ) {} - catch( IOException e ) {}; - - HttpResponse response = null, responseError = null; - HttpGet request; - long totalLen = 0; - CountingInputStream stream; - byte[] buf = new byte[16384]; - boolean DoNotUnzip = false; - boolean FileInAssets = false; - String url = ""; - long partialDownloadLen = 0; - - int downloadUrlIndex = 1; - while( downloadUrlIndex < downloadUrls.length ) - { - Log.i("SDL", "Processing download " + downloadUrls[downloadUrlIndex]); - url = new String(downloadUrls[downloadUrlIndex]); - DoNotUnzip = false; - if(url.indexOf(":") == 0) - { - path = getOutFilePath(url.substring( 1, url.indexOf(":", 1) )); - url = url.substring( url.indexOf(":", 1) + 1 ); - DoNotUnzip = true; - DownloadCanBeResumed = true; - File partialDownload = new File( path ); - if( partialDownload.exists() && !partialDownload.isDirectory() && !forceOverwrite ) - partialDownloadLen = partialDownload.length(); - } - Status.setText( downloadCount + "/" + downloadTotal + ": " + res.getString(R.string.connecting_to, url) ); - if( url.indexOf("http://") == -1 && url.indexOf("https://") == -1 ) // File inside assets - { - InputStream stream1 = null; - try { - stream1 = Parent.getAssets().open(url); - stream1.close(); - } catch( Exception e ) { - try { - stream1 = Parent.getAssets().open(url + "000"); - stream1.close(); - } catch( Exception ee ) { - Log.i("SDL", "Failed to open file in assets: " + url); - downloadUrlIndex++; - continue; - } - } - FileInAssets = true; - Log.i("SDL", "Fetching file from assets: " + url); - break; - } - else - { - Log.i("SDL", "Connecting to: " + url); - request = new HttpGet(url); - request.addHeader("Accept", "*/*"); - if( partialDownloadLen > 0 ) { - request.addHeader("Range", "bytes=" + partialDownloadLen + "-"); - Log.i("SDL", "Trying to resume download at pos " + partialDownloadLen); - } - try { - DefaultHttpClient client = HttpWithDisabledSslCertCheck(); - client.getParams().setBooleanParameter("http.protocol.handle-redirects", true); - response = client.execute(request); - } catch (IOException e) { - Log.i("SDL", "Failed to connect to " + url); - downloadUrlIndex++; - }; - if( response != null ) - { - if( response.getStatusLine().getStatusCode() != 200 && response.getStatusLine().getStatusCode() != 206 ) - { - Log.i("SDL", "Failed to connect to " + url + " with error " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase()); - responseError = response; - response = null; - downloadUrlIndex++; - } - else - break; - } - } - } - if( FileInAssets ) - { - int multipartCounter = 0; - InputStream multipart = null; - while( true ) - { - try { - // Make string ".zip000", ".zip001" etc for multipart archives - String url1 = url + String.format("%03d", multipartCounter); - CountingInputStream stream1 = new CountingInputStream(Parent.getAssets().open(url1), 8192); - while( stream1.skip(65536) > 0 ) { }; - totalLen += stream1.getBytesRead(); - stream1.close(); - InputStream s = Parent.getAssets().open(url1); - if( multipart == null ) - multipart = s; - else - multipart = new SequenceInputStream(multipart, s); - Log.i("SDL", "Multipart archive found: " + url1); - } catch( IOException e ) { - break; - } - multipartCounter += 1; - } - if( multipart != null ) - stream = new CountingInputStream(multipart, 8192); - else - { - try { - stream = new CountingInputStream(Parent.getAssets().open(url), 8192); - while( stream.skip(65536) > 0 ) { }; - totalLen += stream.getBytesRead(); - stream.close(); - stream = new CountingInputStream(Parent.getAssets().open(url), 8192); - } catch( IOException e ) { - Log.i("SDL", "Unpacking from assets '" + url + "' - error: " + e.toString()); - Status.setText( res.getString(R.string.error_dl_from, url) ); - return false; - } - } - } - else - { - if( response == null ) - { - Log.i("SDL", "Error connecting to " + url); - Status.setText( res.getString(R.string.failed_connecting_to, url) + (responseError == null ? "" : ": " + responseError.getStatusLine().getStatusCode() + " " + responseError.getStatusLine().getReasonPhrase()) ); - return false; - } - - Status.setText( downloadCount + "/" + downloadTotal + ": " + res.getString(R.string.dl_from, url) ); - totalLen = response.getEntity().getContentLength(); - try { - stream = new CountingInputStream(response.getEntity().getContent(), 8192); - } catch( java.io.IOException e ) { - Status.setText( res.getString(R.string.error_dl_from, url) ); - return false; - } - } - - long updateStatusTime = 0; - - if(DoNotUnzip) - { - Log.i("SDL", "Saving file '" + path + "'"); - OutputStream out = null; - try { - try { - File outDir = new File( path.substring(0, path.lastIndexOf("/") )); - if( !(outDir.exists() && outDir.isDirectory()) ) - outDir.mkdirs(); - } catch( SecurityException e ) { }; - - if( partialDownloadLen > 0 ) - { - try { - Header[] range = response.getHeaders("Content-Range"); - if( range.length > 0 && range[0].getValue().indexOf("bytes") == 0 ) - { - //Log.i("SDL", "Resuming download of file '" + path + "': Content-Range: " + range[0].getValue()); - String[] skippedBytes = range[0].getValue().split("/")[0].split("-")[0].split(" "); - if( skippedBytes.length >= 2 && Long.parseLong(skippedBytes[1]) == partialDownloadLen ) - { - out = new FileOutputStream( path, true ); - Log.i("SDL", "Resuming download of file '" + path + "' at pos " + partialDownloadLen); - } - } - else - Log.i("SDL", "Server does not support partial downloads. " + (range.length == 0 ? "" : range[0].getValue())); - } catch (Exception e) { } - } - if( out == null ) - { - out = new FileOutputStream( path ); - partialDownloadLen = 0; - } - } catch( FileNotFoundException e ) { - Log.i("SDL", "Saving file '" + path + "' - error creating output file: " + e.toString()); - } catch( SecurityException e ) { - Log.i("SDL", "Saving file '" + path + "' - error creating output file: " + e.toString()); - }; - if( out == null ) - { - Status.setText( res.getString(R.string.error_write, path) ); - Log.i("SDL", "Saving file '" + path + "' - error creating output file"); - return false; - } - - try { - int len = stream.read(buf); - while (len >= 0) - { - if(len > 0) - out.write(buf, 0, len); - len = stream.read(buf); - - float percent = 0.0f; - if( totalLen > 0 ) - percent = (stream.getBytesRead() + partialDownloadLen) * 100.0f / (totalLen + partialDownloadLen); - if( System.currentTimeMillis() > updateStatusTime + 1000 ) - { - updateStatusTime = System.currentTimeMillis(); - Status.setText( downloadCount + "/" + downloadTotal + ": " + res.getString(R.string.dl_progress, percent, path) ); - } - } - out.flush(); - out.close(); - out = null; - } catch( java.io.IOException e ) { - Status.setText( res.getString(R.string.error_write, path) + ": " + e.getMessage() ); - Log.i("SDL", "Saving file '" + path + "' - error writing: " + e.toString()); - return false; - } - Log.i("SDL", "Saving file '" + path + "' done"); - } - else - { - Log.i("SDL", "Reading from zip file '" + url + "'"); - ZipInputStream zip = new ZipInputStream(stream); - - while(true) - { - ZipEntry entry = null; - try { - entry = zip.getNextEntry(); - if( entry != null ) - Log.i("SDL", "Reading from zip file '" + url + "' entry '" + entry.getName() + "'"); - } catch( java.io.IOException e ) { - Status.setText( res.getString(R.string.error_dl_from, url) ); - Log.i("SDL", "Error reading from zip file '" + url + "': " + e.toString()); - return false; - } - if( entry == null ) - { - Log.i("SDL", "Reading from zip file '" + url + "' finished"); - break; - } - if( entry.isDirectory() ) - { - Log.i("SDL", "Creating dir '" + getOutFilePath(entry.getName()) + "'"); - try { - File outDir = new File( getOutFilePath(entry.getName()) ); - if( !(outDir.exists() && outDir.isDirectory()) ) - outDir.mkdirs(); - } catch( SecurityException e ) { }; - continue; - } - - OutputStream out = null; - path = getOutFilePath(entry.getName()); - float percent = 0.0f; - - Log.i("SDL", "Saving file '" + path + "'"); - - try { - File outDir = new File( path.substring(0, path.lastIndexOf("/") )); - if( !(outDir.exists() && outDir.isDirectory()) ) - outDir.mkdirs(); - } catch( SecurityException e ) { }; - - try { - CheckedInputStream check = new CheckedInputStream( new FileInputStream(path), new CRC32() ); - while( check.read(buf, 0, buf.length) >= 0 ) {}; - check.close(); - if( check.getChecksum().getValue() != entry.getCrc() ) - { - File ff = new File(path); - ff.delete(); - throw new Exception(); - } - Log.i("SDL", "File '" + path + "' exists and passed CRC check - not overwriting it"); - if( totalLen > 0 ) - percent = stream.getBytesRead() * 100.0f / totalLen; - if( System.currentTimeMillis() > updateStatusTime + 1000 ) - { - updateStatusTime = System.currentTimeMillis(); - Status.setText( downloadCount + "/" + downloadTotal + ": " + res.getString(R.string.dl_progress, percent, path) ); - } - continue; - } catch( Exception e ) { } - - try { - out = new FileOutputStream( path ); - } catch( FileNotFoundException e ) { - Log.i("SDL", "Saving file '" + path + "' - cannot create file: " + e.toString()); - } catch( SecurityException e ) { - Log.i("SDL", "Saving file '" + path + "' - cannot create file: " + e.toString()); - }; - if( out == null ) - { - Status.setText( res.getString(R.string.error_write, path) ); - Log.i("SDL", "Saving file '" + path + "' - cannot create file"); - return false; - } - - if( totalLen > 0 ) - percent = stream.getBytesRead() * 100.0f / totalLen; - if( System.currentTimeMillis() > updateStatusTime + 1000 ) - { - updateStatusTime = System.currentTimeMillis(); - Status.setText( downloadCount + "/" + downloadTotal + ": " + res.getString(R.string.dl_progress, percent, path) ); - } - - try { - int len = zip.read(buf); - while (len >= 0) - { - if(len > 0) - out.write(buf, 0, len); - len = zip.read(buf); - - percent = 0.0f; - if( totalLen > 0 ) - percent = stream.getBytesRead() * 100.0f / totalLen; - if( System.currentTimeMillis() > updateStatusTime + 1000 ) - { - updateStatusTime = System.currentTimeMillis(); - Status.setText( downloadCount + "/" + downloadTotal + ": " + res.getString(R.string.dl_progress, percent, path) ); - } - } - out.flush(); - out.close(); - out = null; - } catch( java.io.IOException e ) { - Status.setText( res.getString(R.string.error_write, path) + ": " + e.getMessage() ); - Log.i("SDL", "Saving file '" + path + "' - error writing or downloading: " + e.toString()); - return false; - } - - try { - long count = 0, ret = 0; - CheckedInputStream check = new CheckedInputStream( new FileInputStream(path), new CRC32() ); - while( ret >= 0 ) - { - count += ret; - ret = check.read(buf, 0, buf.length); - } - check.close(); - if( check.getChecksum().getValue() != entry.getCrc() || count != entry.getSize() ) - { - File ff = new File(path); - ff.delete(); - Log.i("SDL", "Saving file '" + path + "' - CRC check failed, ZIP: " + - String.format("%x", entry.getCrc()) + " actual file: " + String.format("%x", check.getChecksum().getValue()) + - " file size in ZIP: " + entry.getSize() + " actual size " + count ); - throw new Exception(); - } - } catch( Exception e ) { - Status.setText( res.getString(R.string.error_write, path) + ": " + e.getMessage() ); - return false; - } - Log.i("SDL", "Saving file '" + path + "' done"); - } - }; - - OutputStream out = null; - path = getOutFilePath(DownloadFlagFileName); - try { - out = new FileOutputStream( path ); - out.write(downloadUrls[downloadUrlIndex].getBytes("UTF-8")); - out.flush(); - out.close(); - } catch( FileNotFoundException e ) { - } catch( SecurityException e ) { - } catch( java.io.IOException e ) { - Status.setText( res.getString(R.string.error_write, path) + ": " + e.getMessage() ); - return false; - }; - Status.setText( downloadCount + "/" + downloadTotal + ": " + res.getString(R.string.dl_finished) ); - - try { - stream.close(); - } catch( java.io.IOException e ) { - }; - - return true; - }; - - private void initParent() - { - class Callback implements Runnable - { - public MainActivity Parent; - public void run() - { - Parent.initSDL(); - } - } - Callback cb = new Callback(); - synchronized(this) { - cb.Parent = Parent; - if(Parent != null) - Parent.runOnUiThread(cb); - } - } - - private String getOutFilePath(final String filename) - { - return outFilesDir + "/" + filename; - }; - - private static DefaultHttpClient HttpWithDisabledSslCertCheck() - { - return new DefaultHttpClient(); - // This code does not work - /* - HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER; - - DefaultHttpClient client = new DefaultHttpClient(); - - SchemeRegistry registry = new SchemeRegistry(); - SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory(); - socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier); - registry.register(new Scheme("https", socketFactory, 443)); - SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry); - DefaultHttpClient http = new DefaultHttpClient(mgr, client.getParams()); - - HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); - - return http; - */ - } - - - public class BackKeyListener implements MainActivity.KeyEventsListener - { - MainActivity p; - public BackKeyListener(MainActivity _p) - { - p = _p; - } - - public void onKeyEvent(final int keyCode) - { - if( DownloadFailed ) - System.exit(1); - - AlertDialog.Builder builder = new AlertDialog.Builder(p); - builder.setTitle(p.getResources().getString(R.string.cancel_download)); - builder.setMessage(p.getResources().getString(R.string.cancel_download) + (DownloadCanBeResumed ? " " + p.getResources().getString(R.string.cancel_download_resume) : "")); - - builder.setPositiveButton(p.getResources().getString(R.string.yes), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - System.exit(1); - dialog.dismiss(); - } - }); - builder.setNegativeButton(p.getResources().getString(R.string.no), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int item) - { - dialog.dismiss(); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() - { - public void onCancel(DialogInterface dialog) - { - } - }); - AlertDialog alert = builder.create(); - alert.setOwnerActivity(p); - alert.show(); - } - } - - public StatusWriter Status; - public boolean DownloadComplete = false; - public boolean DownloadFailed = false; - public boolean DownloadCanBeResumed = false; - private MainActivity Parent; - private String outFilesDir = null; -} - diff --git a/project/javaSDL2/GLSurfaceView_SDL.java b/project/javaSDL2/GLSurfaceView_SDL.java deleted file mode 100644 index e695490a9..000000000 --- a/project/javaSDL2/GLSurfaceView_SDL.java +++ /dev/null @@ -1,1278 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* This is GLSurfaceView class ripped out of Android 2.1 sources, - fixed with a hammer to work with libSDL port */ - -package net.sourceforge.clonekeenplus; - -import java.io.Writer; -import java.util.ArrayList; -import java.util.concurrent.Semaphore; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGL11; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; -import javax.microedition.khronos.opengles.GL; -import javax.microedition.khronos.opengles.GL10; - -import android.content.Context; -import android.util.AttributeSet; -import android.util.Log; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.app.KeyguardManager; - -/** - * An implementation of SurfaceView that uses the dedicated surface for - * displaying OpenGL rendering. - *

- * A GLSurfaceView provides the following features: - *

- *

- * - *

Using GLSurfaceView

- *

- * Typically you use GLSurfaceView by subclassing it and overriding one or more of the - * View system input event methods. If your application does not need to override event - * methods then GLSurfaceView can be used as-is. For the most part - * GLSurfaceView behavior is customized by calling "set" methods rather than by subclassing. - * For example, unlike a regular View, drawing is delegated to a separate Renderer object which - * is registered with the GLSurfaceView - * using the {@link #setRenderer(Renderer)} call. - *

- *

Initializing GLSurfaceView

- * All you have to do to initialize a GLSurfaceView is call {@link #setRenderer(Renderer)}. - * However, if desired, you can modify the default behavior of GLSurfaceView by calling one or - * more of these methods before calling setRenderer: - * - *

- *

Choosing an EGL Configuration

- * A given Android device may support multiple possible types of drawing surfaces. - * The available surfaces may differ in how may channels of data are present, as - * well as how many bits are allocated to each channel. Therefore, the first thing - * GLSurfaceView has to do when starting to render is choose what type of surface to use. - *

- * By default GLSurfaceView chooses an available surface that's closest to a 16-bit R5G6B5 surface - * with a 16-bit depth buffer and no stencil. If you would prefer a different surface (for example, - * if you do not need a depth buffer) you can override the default behavior by calling one of the - * setEGLConfigChooser methods. - *

- *

Debug Behavior

- * You can optionally modify the behavior of GLSurfaceView by calling - * one or more of the debugging methods {@link #setDebugFlags(int)}, - * and {@link #setGLWrapper}. These methods may be called before and/or after setRenderer, but - * typically they are called before setRenderer so that they take effect immediately. - *

- *

Setting a Renderer

- * Finally, you must call {@link #setRenderer} to register a {@link Renderer}. - * The renderer is - * responsible for doing the actual OpenGL rendering. - *

- *

Rendering Mode

- * Once the renderer is set, you can control whether the renderer draws - * continuously or on-demand by calling - * {@link #setRenderMode}. The default is continuous rendering. - *

- *

Activity Life-cycle

- * A GLSurfaceView must be notified when the activity is paused and resumed. GLSurfaceView clients - * are required to call {@link #onPause()} when the activity pauses and - * {@link #onResume()} when the activity resumes. These calls allow GLSurfaceView to - * pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate - * the OpenGL display. - *

- *

Handling events

- *

- * To handle an event you will typically subclass GLSurfaceView and override the - * appropriate method, just as you would with any other View. However, when handling - * the event, you may need to communicate with the Renderer object - * that's running in the rendering thread. You can do this using any - * standard Java cross-thread communication mechanism. In addition, - * one relatively easy way to communicate with your renderer is - * to call - * {@link #queueEvent(Runnable)}. For example: - *

- * class MyGLSurfaceView extends GLSurfaceView {
- *
- *     private MyRenderer mMyRenderer;
- *
- *     public void start() {
- *         mMyRenderer = ...;
- *         setRenderer(mMyRenderer);
- *     }
- *
- *     public boolean onKeyDown(int keyCode, KeyEvent event) {
- *         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
- *             queueEvent(new Runnable() {
- *                 // This method will be called on the rendering
- *                 // thread:
- *                 public void run() {
- *                     mMyRenderer.handleDpadCenter();
- *                 }});
- *             return true;
- *         }
- *         return super.onKeyDown(keyCode, event);
- *     }
- * }
- * 
- * - */ -public class GLSurfaceView_SDL extends SurfaceView implements SurfaceHolder.Callback { - /** - * The renderer only renders - * when the surface is created, or when {@link #requestRender} is called. - * - * @see #getRenderMode() - * @see #setRenderMode(int) - */ - public final static int RENDERMODE_WHEN_DIRTY = 0; - /** - * The renderer is called - * continuously to re-render the scene. - * - * @see #getRenderMode() - * @see #setRenderMode(int) - * @see #requestRender() - */ - public final static int RENDERMODE_CONTINUOUSLY = 1; - - /** - * Check glError() after every GL call and throw an exception if glError indicates - * that an error has occurred. This can be used to help track down which OpenGL ES call - * is causing an error. - * - * @see #getDebugFlags - * @see #setDebugFlags - */ - public final static int DEBUG_CHECK_GL_ERROR = 1; - - /** - * Log GL calls to the system log at "verbose" level with tag "GLSurfaceView". - * - * @see #getDebugFlags - * @see #setDebugFlags - */ - public final static int DEBUG_LOG_GL_CALLS = 2; - - /** - * Standard View constructor. In order to render something, you - * must call {@link #setRenderer} to register a renderer. - */ - public GLSurfaceView_SDL(Context context) { - super(context); - init(); - } - - /** - * Standard View constructor. In order to render something, you - * must call {@link #setRenderer} to register a renderer. - */ - public GLSurfaceView_SDL(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - private void init() { - // Install a SurfaceHolder.Callback so we get notified when the - // underlying surface is created and destroyed - SurfaceHolder holder = getHolder(); - holder.addCallback(this); - holder.setType(SurfaceHolder.SURFACE_TYPE_GPU); - } - - /** - * Set the glWrapper. If the glWrapper is not null, its - * {@link GLWrapper#wrap(GL)} method is called - * whenever a surface is created. A GLWrapper can be used to wrap - * the GL object that's passed to the renderer. Wrapping a GL - * object enables examining and modifying the behavior of the - * GL calls made by the renderer. - *

- * Wrapping is typically used for debugging purposes. - *

- * The default value is null. - * @param glWrapper the new GLWrapper - */ - public void setGLWrapper(GLWrapper glWrapper) { - mGLWrapper = glWrapper; - } - - /** - * Set the debug flags to a new value. The value is - * constructed by OR-together zero or more - * of the DEBUG_CHECK_* constants. The debug flags take effect - * whenever a surface is created. The default value is zero. - * @param debugFlags the new debug flags - * @see #DEBUG_CHECK_GL_ERROR - * @see #DEBUG_LOG_GL_CALLS - */ - public void setDebugFlags(int debugFlags) { - mDebugFlags = debugFlags; - } - - /** - * Get the current value of the debug flags. - * @return the current value of the debug flags. - */ - public int getDebugFlags() { - return mDebugFlags; - } - - /** - * Set the renderer associated with this view. Also starts the thread that - * will call the renderer, which in turn causes the rendering to start. - *

This method should be called once and only once in the life-cycle of - * a GLSurfaceView. - *

The following GLSurfaceView methods can only be called before - * setRenderer is called: - *

- *

- * The following GLSurfaceView methods can only be called after - * setRenderer is called: - *

- * - * @param renderer the renderer to use to perform OpenGL drawing. - */ - public void setRenderer(Renderer renderer) { - if (mGLThread != null) { - throw new IllegalStateException( - "setRenderer has already been called for this instance."); - } - if (mEGLConfigChooser == null) { - mEGLConfigChooser = getEglConfigChooser(16, false, false, false); - } - mGLThread = new GLThread(renderer); - mGLThread.start(); - } - - /** - * Install a custom EGLConfigChooser. - *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} - * is called. - *

- * If no setEGLConfigChooser method is called, then by default the - * view will choose a config as close to 16-bit RGB as possible, with - * a depth buffer as close to 16 bits as possible. - * @param configChooser - */ - public void setEGLConfigChooser(EGLConfigChooser configChooser) { - if (mGLThread != null) { - throw new IllegalStateException( - "setRenderer has already been called for this instance."); - } - mEGLConfigChooser = configChooser; - } - - /** - * Install a config chooser which will choose a config - * as close to 16-bit RGB as possible, with or without an optional depth - * buffer as close to 16-bits as possible. - *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} - * is called. - *

- * If no setEGLConfigChooser method is called, then by default the - * view will choose a config as close to 16-bit RGB as possible, with - * a depth buffer as close to 16 bits as possible. - * - * @param needDepth - */ - public void setEGLConfigChooser(int bpp, boolean needDepth, boolean stencil, boolean gles2) { - setEGLConfigChooser(getEglConfigChooser(bpp, needDepth, stencil, gles2)); - } - - /** - * Install a config chooser which will choose a config - * with at least the specified component sizes, and as close - * to the specified component sizes as possible. - *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} - * is called. - *

- * If no setEGLConfigChooser method is called, then by default the - * view will choose a config as close to 16-bit RGB as possible, with - * a depth buffer as close to 16 bits as possible. - * - */ - public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, - int alphaSize, int depthSize, int stencilSize, boolean gles2) { - setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize, - blueSize, alphaSize, depthSize, stencilSize, gles2)); - } - /** - * Set the rendering mode. When renderMode is - * RENDERMODE_CONTINUOUSLY, the renderer is called - * repeatedly to re-render the scene. When renderMode - * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface - * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY. - *

- * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance - * by allowing the GPU and CPU to idle when the view does not need to be updated. - *

- * This method can only be called after {@link #setRenderer(Renderer)} - * - * @param renderMode one of the RENDERMODE_X constants - * @see #RENDERMODE_CONTINUOUSLY - * @see #RENDERMODE_WHEN_DIRTY - */ - public void setRenderMode(int renderMode) { - mGLThread.setRenderMode(renderMode); - } - - /** - * Get the current rendering mode. May be called - * from any thread. Must not be called before a renderer has been set. - * @return the current rendering mode. - * @see #RENDERMODE_CONTINUOUSLY - * @see #RENDERMODE_WHEN_DIRTY - */ - public int getRenderMode() { - return mGLThread.getRenderMode(); - } - - /** - * Request that the renderer render a frame. - * This method is typically used when the render mode has been set to - * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand. - * May be called - * from any thread. Must not be called before a renderer has been set. - */ - public void requestRender() { - mGLThread.requestRender(); - } - - /** - * This method is part of the SurfaceHolder.Callback interface, and is - * not normally called or subclassed by clients of GLSurfaceView. - */ - public void surfaceCreated(SurfaceHolder holder) { - mGLThread.surfaceCreated(); - } - - /** - * This method is part of the SurfaceHolder.Callback interface, and is - * not normally called or subclassed by clients of GLSurfaceView. - */ - public void surfaceDestroyed(SurfaceHolder holder) { - // Surface will be destroyed when we return - mGLThread.surfaceDestroyed(); - } - - /** - * This method is part of the SurfaceHolder.Callback interface, and is - * not normally called or subclassed by clients of GLSurfaceView. - */ - public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { - mGLThread.onWindowResize(w, h); - } - - /** - * Inform the view that the activity is paused. The owner of this view must - * call this method when the activity is paused. Calling this method will - * pause the rendering thread. - * Must not be called before a renderer has been set. - */ - public void onPause() { - mGLThread.onPause(); - } - - /** - * Inform the view that the activity is resumed. The owner of this view must - * call this method when the activity is resumed. Calling this method will - * recreate the OpenGL display and resume the rendering - * thread. - * Must not be called before a renderer has been set. - */ - public void onResume() { - mGLThread.onResume(); - } - - /** - * Queue a runnable to be run on the GL rendering thread. This can be used - * to communicate with the Renderer on the rendering thread. - * Must not be called before a renderer has been set. - * @param r the runnable to be run on the GL rendering thread. - */ - public void queueEvent(Runnable r) { - mGLThread.queueEvent(r); - } - - /** - * This method is used as part of the View class and is not normally - * called or subclassed by clients of GLSurfaceView. - * Must not be called before a renderer has been set. - */ - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mGLThread.requestExitAndWait(); - } - - // ---------------------------------------------------------------------- - - /** - * An interface used to wrap a GL interface. - *

Typically - * used for implementing debugging and tracing on top of the default - * GL interface. You would typically use this by creating your own class - * that implemented all the GL methods by delegating to another GL instance. - * Then you could add your own behavior before or after calling the - * delegate. All the GLWrapper would do was instantiate and return the - * wrapper GL instance: - *

-     * class MyGLWrapper implements GLWrapper {
-     *     GL wrap(GL gl) {
-     *         return new MyGLImplementation(gl);
-     *     }
-     *     static class MyGLImplementation implements GL,GL10,GL11,... {
-     *         ...
-     *     }
-     * }
-     * 
- * @see #setGLWrapper(GLWrapper) - */ - public interface GLWrapper { - /** - * Wraps a gl interface in another gl interface. - * @param gl a GL interface that is to be wrapped. - * @return either the input argument or another GL object that wraps the input argument. - */ - GL wrap(GL gl); - } - - /** - * A generic renderer interface. - *

- * The renderer is responsible for making OpenGL calls to render a frame. - *

- * GLSurfaceView clients typically create their own classes that implement - * this interface, and then call {@link GLSurfaceView#setRenderer} to - * register the renderer with the GLSurfaceView. - *

- *

Threading

- * The renderer will be called on a separate thread, so that rendering - * performance is decoupled from the UI thread. Clients typically need to - * communicate with the renderer from the UI thread, because that's where - * input events are received. Clients can communicate using any of the - * standard Java techniques for cross-thread communication, or they can - * use the {@link GLSurfaceView#queueEvent(Runnable)} convenience method. - *

- *

EGL Context Lost

- * There are situations where the EGL rendering context will be lost. This - * typically happens when device wakes up after going to sleep. When - * the EGL context is lost, all OpenGL resources (such as textures) that are - * associated with that context will be automatically deleted. In order to - * keep rendering correctly, a renderer must recreate any lost resources - * that it still needs. The {@link #onSurfaceCreated(GL10, EGLConfig)} method - * is a convenient place to do this. - * - * - * @see #setRenderer(Renderer) - */ - public static interface SwapBuffersCallback { - public boolean SwapBuffers(); - } - - public static abstract class Renderer { - /** - * Called when the surface is created or recreated. - *

- * Called when the rendering thread - * starts and whenever the EGL context is lost. The context will typically - * be lost when the Android device awakes after going to sleep. - *

- * Since this method is called at the beginning of rendering, as well as - * every time the EGL context is lost, this method is a convenient place to put - * code to create resources that need to be created when the rendering - * starts, and that need to be recreated when the EGL context is lost. - * Textures are an example of a resource that you might want to create - * here. - *

- * Note that when the EGL context is lost, all OpenGL resources associated - * with that context will be automatically deleted. You do not need to call - * the corresponding "glDelete" methods such as glDeleteTextures to - * manually delete these lost resources. - *

- * @param gl the GL interface. Use instanceof to - * test if the interface supports GL11 or higher interfaces. - * @param config the EGLConfig of the created surface. Can be used - * to create matching pbuffers. - */ - public abstract void onSurfaceCreated(GL10 gl, EGLConfig config); - - public abstract void onSurfaceDestroyed(); - - /** - * Called when the surface changed size. - *

- * Called after the surface is created and whenever - * the OpenGL ES surface size changes. - *

- * Typically you will set your viewport here. If your camera - * is fixed then you could also set your projection matrix here: - *

-         * void onSurfaceChanged(GL10 gl, int width, int height) {
-         *     gl.glViewport(0, 0, width, height);
-         *     // for a fixed camera, set the projection too
-         *     float ratio = (float) width / height;
-         *     gl.glMatrixMode(GL10.GL_PROJECTION);
-         *     gl.glLoadIdentity();
-         *     gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
-         * }
-         * 
- * @param gl the GL interface. Use instanceof to - * test if the interface supports GL11 or higher interfaces. - * @param width - * @param height - */ - public abstract void onSurfaceChanged(GL10 gl, int width, int height); - - /** - * Called to draw the current frame. - *

- * This method is responsible for drawing the current frame. - *

- * The implementation of this method typically looks like this: - *

-         * void onDrawFrame(GL10 gl) {
-         *     gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
-         *     //... other gl calls to render the scene ...
-         * }
-         * 
- * @param gl the GL interface. Use instanceof to - * test if the interface supports GL11 or higher interfaces. - */ - public abstract void onDrawFrame(GL10 gl); - - public boolean SwapBuffers() { - if( mSwapBuffersCallback != null ) - return mSwapBuffersCallback.SwapBuffers(); - return false; - } - - public void setSwapBuffersCallback( SwapBuffersCallback c ) { - mSwapBuffersCallback = c; - } - - private SwapBuffersCallback mSwapBuffersCallback = null; - } - - /** - * An interface for choosing an EGLConfig configuration from a list of - * potential configurations. - *

- * This interface must be implemented by clients wishing to call - * {@link GLSurfaceView#setEGLConfigChooser(EGLConfigChooser)} - */ - public interface EGLConfigChooser { - /** - * Choose a configuration from the list. Implementors typically - * implement this method by calling - * {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the - * EGL specification available from The Khronos Group to learn how to call eglChooseConfig. - * @param egl the EGL10 for the current display. - * @param display the current display. - * @return the chosen configuration. - */ - EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); - public boolean isGles2Required(); - } - - private static abstract class BaseConfigChooser - implements EGLConfigChooser { - public BaseConfigChooser(int[] configSpec) { - mConfigSpec = configSpec; - } - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { - int[] num_config = new int[1]; - egl.eglChooseConfig(display, mConfigSpec, null, 0, num_config); - - int numConfigs = num_config[0]; - - if (numConfigs <= 0) { - throw new IllegalArgumentException( - "No configs match configSpec"); - } - - EGLConfig[] configs = new EGLConfig[numConfigs]; - egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, - num_config); - EGLConfig config = chooseConfig(egl, display, configs); - if (config == null) { - throw new IllegalArgumentException("No config chosen"); - } - return config; - } - - abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, - EGLConfig[] configs); - - protected int[] mConfigSpec; - } - - private static class ComponentSizeChooser extends BaseConfigChooser { - public ComponentSizeChooser(int redSize, int greenSize, int blueSize, - int alphaSize, int depthSize, int stencilSize, boolean isGles2) { - super(new int[] {EGL10.EGL_NONE}); // Get all possible configs - mValue = new int[1]; - mRedSize = redSize; - mGreenSize = greenSize; - mBlueSize = blueSize; - mAlphaSize = alphaSize; - mDepthSize = depthSize; - mStencilSize = stencilSize; - mIsGles2 = isGles2; - } - - @Override - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, - EGLConfig[] configs) { - EGLConfig closestConfig = null; - int closestDistance = 1000; - String cfglog = ""; - int idx = 0; - int selectidx = -1; - - Log.v("SDL", "Desired GL config: " + "R" + mRedSize + "G" + mGreenSize + "B" + mBlueSize + "A" + mAlphaSize + " depth " + mDepthSize + " stencil " + mStencilSize + " type " + (mIsGles2 ? "GLES2" : "GLES")); - for(EGLConfig config : configs) { - if ( config == null ) - continue; - int r = findConfigAttrib(egl, display, config, - EGL10.EGL_RED_SIZE, 0); - int g = findConfigAttrib(egl, display, config, - EGL10.EGL_GREEN_SIZE, 0); - int b = findConfigAttrib(egl, display, config, - EGL10.EGL_BLUE_SIZE, 0); - int a = findConfigAttrib(egl, display, config, - EGL10.EGL_ALPHA_SIZE, 0); - int d = findConfigAttrib(egl, display, config, - EGL10.EGL_DEPTH_SIZE, 0); - int s = findConfigAttrib(egl, display, config, - EGL10.EGL_STENCIL_SIZE, 0); - int rendertype = findConfigAttrib(egl, display, config, - EGL10.EGL_RENDERABLE_TYPE, 0); - int desiredtype = mIsGles2 ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES_BIT; - int nativeRender = findConfigAttrib(egl, display, config, - EGL10.EGL_NATIVE_RENDERABLE, 0); - int caveat = findConfigAttrib(egl, display, config, - EGL10.EGL_CONFIG_CAVEAT, EGL10.EGL_NONE); - int distance = Math.abs(r - mRedSize) + Math.abs(g - mGreenSize) + Math.abs(b - mBlueSize); - int dist1 = distance; - if( mAlphaSize - a > 0 ) - distance += mAlphaSize - a; - else if( mAlphaSize - a < 0 ) - distance += 1; // Small penalty if we don't need alpha channel but it is present - int dist2 = distance; - if( (d > 0) != (mDepthSize > 0) ) - distance += (mDepthSize > 0) ? 5 : 1; // Small penalty if we don't need zbuffer but it is present - int dist3 = distance; - if( (s > 0) != (mStencilSize > 0) ) - distance += (mStencilSize > 0) ? 5 : 1; // Small penalty if we don't need stencil buffer but it is present - int dist4 = distance; - if( (rendertype & desiredtype) == 0 ) - distance += 5; - int dist5 = distance; - if( caveat == EGL10.EGL_SLOW_CONFIG ) - distance += 4; - if( caveat == EGL10.EGL_NON_CONFORMANT_CONFIG ) // dunno what that means, probably R and B channels swapped - distance += 1; - - String cfgcur = "R" + r + "G" + g + "B" + b + "A" + a + " depth " + d + " stencil " + s + - " type " + rendertype + " ("; - if((rendertype & EGL_OPENGL_ES_BIT) != 0) - cfgcur += "GLES"; - if((rendertype & EGL_OPENGL_ES2_BIT) != 0) - cfgcur += " GLES2"; - if((rendertype & EGL_OPENGL_BIT) != 0) - cfgcur += " OPENGL"; - if((rendertype & EGL_OPENVG_BIT) != 0) - cfgcur += " OPENVG"; - cfgcur += ")"; - cfgcur += " caveat " + (caveat == EGL10.EGL_NONE ? "none" : - (caveat == EGL10.EGL_SLOW_CONFIG ? "SLOW" : - caveat == EGL10.EGL_NON_CONFORMANT_CONFIG ? "non-conformant" : - String.valueOf(caveat))); - cfgcur += " nr " + nativeRender; - cfgcur += " pos " + distance + " (" + dist1 + "," + dist2 + "," + dist3 + "," + dist4 + "," + dist5 + ")"; - Log.v("SDL", "GL config " + idx + ": " + cfgcur); - if (distance < closestDistance) { - closestDistance = distance; - closestConfig = config; - cfglog = new String(cfgcur); - selectidx = idx; - } - idx += 1; - } - Log.v("SDL", "GLSurfaceView_SDL::EGLConfigChooser::chooseConfig(): selected " + selectidx + ": " + cfglog ); - return closestConfig; - } - - private int findConfigAttrib(EGL10 egl, EGLDisplay display, - EGLConfig config, int attribute, int defaultValue) { - mValue[0] = -1; - if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { - return mValue[0]; - } - Log.w("SDL", "GLSurfaceView_SDL::EGLConfigChooser::findConfigAttrib(): attribute doesn't exist: " + attribute); - return defaultValue; - } - - public boolean isGles2Required() - { - return mIsGles2; - } - - private int[] mValue; - // Subclasses can adjust these values: - protected int mRedSize; - protected int mGreenSize; - protected int mBlueSize; - protected int mAlphaSize; - protected int mDepthSize; - protected int mStencilSize; - protected boolean mIsGles2 = false; - - public static final int EGL_OPENGL_ES_BIT = 1; - public static final int EGL_OPENVG_BIT = 2; - public static final int EGL_OPENGL_ES2_BIT = 4; - public static final int EGL_OPENGL_BIT = 8; - } - - /** - * This class will choose a supported surface as close to - * RGB565 as possible, with or without a depth buffer. - * - */ - private static class SimpleEGLConfigChooser16 extends ComponentSizeChooser { - public SimpleEGLConfigChooser16(boolean withDepthBuffer, boolean stencil, boolean gles2) { - super(4, 4, 4, 0, withDepthBuffer ? 16 : 0, stencil ? 8 : 0, gles2); - // Adjust target values. This way we'll accept a 4444 or - // 555 buffer if there's no 565 buffer available. - mRedSize = 5; - mGreenSize = 6; - mBlueSize = 5; - } - } - - private static class SimpleEGLConfigChooser24 extends ComponentSizeChooser { - public SimpleEGLConfigChooser24(boolean withDepthBuffer, boolean stencil, boolean gles2) { - super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, stencil ? 8 : 0, gles2); - mRedSize = 8; - mGreenSize = 8; - mBlueSize = 8; - } - } - - private static class SimpleEGLConfigChooser32 extends ComponentSizeChooser { - public SimpleEGLConfigChooser32(boolean withDepthBuffer, boolean stencil, boolean gles2) { - super(8, 8, 8, 8, withDepthBuffer ? 16 : 0, stencil ? 8 : 0, gles2); - mRedSize = 8; - mGreenSize = 8; - mBlueSize = 8; - mAlphaSize = 8; - } - } - private static ComponentSizeChooser getEglConfigChooser(int videoDepthBpp, boolean withDepthBuffer, boolean stencil, boolean gles2) { - if(videoDepthBpp == 16) - return new SimpleEGLConfigChooser16(withDepthBuffer, stencil, gles2); - if(videoDepthBpp == 24) - return new SimpleEGLConfigChooser24(withDepthBuffer, stencil, gles2); - if(videoDepthBpp == 32) - return new SimpleEGLConfigChooser32(withDepthBuffer, stencil, gles2); - return null; - }; - - /** - * An EGL helper class. - */ - - private class EglHelper { - public EglHelper() { - - } - - /** - * Initialize EGL for a given configuration spec. - * @param configSpec - */ - public void start(){ - - Log.v("SDL", "GLSurfaceView_SDL::EglHelper::start(): creating GL context"); - /* - * Get an EGL instance - */ - //mEgl = (EGL10) EGLContext.getEGL(); - - /* - * Get to the default display. - */ - //mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); - - /* - * We can now initialize EGL for that display - */ - /*int[] version = new int[2]; - mEgl.eglInitialize(mEglDisplay, version); - mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay); - if( mEglConfig == null ) - Log.e("SDL", "GLSurfaceView_SDL::EglHelper::start(): mEglConfig is NULL");*/ - - /* - * Create an OpenGL ES context. This must be done only once, an - * OpenGL context is a somewhat heavy object. - */ - /*final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - final int[] gles2_attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; - - mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, - EGL10.EGL_NO_CONTEXT, mEGLConfigChooser.isGles2Required() ? gles2_attrib_list : null ); - - if( mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT ) - Log.e("SDL", "GLSurfaceView_SDL::EglHelper::start(): mEglContext is EGL_NO_CONTEXT, error: " + mEgl.eglGetError());*/ - - //mEglSurface = null; - } - - /* - * React to the creation of a new surface by creating and returning an - * OpenGL interface that renders to that surface. - */ - public GL createSurface(SurfaceHolder holder) { - Log.v("SDL", "GLSurfaceView_SDL::EglHelper::createSurface(): creating GL context"); - /* - * The window size has changed, so we need to create a new - * surface. - */ - /*if (mEglSurface != null) {*/ - - /* - * Unbind and destroy the old EGL surface, if - * there is one. - */ - /*mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); - mEgl.eglDestroySurface(mEglDisplay, mEglSurface); - }*/ - - /* - * Create an EGL surface we can render into. - */ - /* - // This does not have any effect on Galaxy Note - int [] attribList = new int[4]; - attribList[0] = mEgl.EGL_RENDER_BUFFER; - attribList[1] = mEgl.EGL_SINGLE_BUFFER; - attribList[2] = mEgl.EGL_NONE; - attribList[3] = mEgl.EGL_NONE; - */ - /*mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, - mEglConfig, holder, null);*/ - - /* - * Before we can issue GL commands, we need to make sure - * the context is current and bound to a surface. - */ - /*mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, - mEglContext);*/ - - - /*GL gl = mEglContext.getGL(); - if (mGLWrapper != null) { - gl = mGLWrapper.wrap(gl); - }*/ - - //return gl; - return null; - } - - /** - * Display the current render surface. - * @return false if the context has been lost. - */ - public boolean swap() { - //mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); - - /* - * Always check for EGL_CONTEXT_LOST, which means the context - * and all associated data were lost (For instance because - * the device went to sleep). We need to sleep until we - * get a new surface. - */ - //return mEgl.eglGetError() != EGL11.EGL_CONTEXT_LOST; - return true; - } - - public void finish() { - Log.v("SDL", "GLSurfaceView_SDL::EglHelper::finish(): destroying GL context"); - /*if (mEglSurface != null) { - mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_CONTEXT); - //mEgl.eglDestroySurface(mEglDisplay, mEglSurface); - //mEglSurface = null; - } - if (mEglContext != null) { - mEgl.eglDestroyContext(mEglDisplay, mEglContext); - mEglContext = null; - } - if (mEglDisplay != null) { - mEgl.eglTerminate(mEglDisplay); - mEglDisplay = null; - }*/ - } - - /*EGL10 mEgl; - EGLDisplay mEglDisplay; - EGLSurface mEglSurface; - EGLConfig mEglConfig; - EGLContext mEglContext;*/ - } - - /** - * A generic GL Thread. Takes care of initializing EGL and GL. Delegates - * to a Renderer instance to do the actual drawing. Can be configured to - * render continuously or on request. - * - */ - class GLThread extends Thread implements SwapBuffersCallback { - GLThread(Renderer renderer) { - super(); - mDone = false; - mWidth = 0; - mHeight = 0; - mRequestRender = true; - mRenderMode = RENDERMODE_CONTINUOUSLY; - mRenderer = renderer; - mRenderer.setSwapBuffersCallback(this); - setName("GLThread"); - } - - @Override - public void run() { - /* - * When the android framework launches a second instance of - * an activity, the new instance's onCreate() method may be - * called before the first instance returns from onDestroy(). - * - * This semaphore ensures that only one instance at a time - * accesses EGL. - */ - try { - sEglSemaphore.acquire(); - } catch (InterruptedException e) { - return; - } - - mEglHelper = new EglHelper(); - // mEglHelper.start(); - mNeedStart = true; - mSizeChanged = true; - SwapBuffers(); - - mRenderer.onDrawFrame(mGL); - - mEglHelper.finish(); - - /* - synchronized (sGLThreadManager) { - stopEglLocked(); - } - sGLThreadManager.threadExiting(this); - */ - - sEglSemaphore.release(); - } - - public boolean SwapBuffers() { - - boolean tellRendererSurfaceCreated = false; - boolean tellRendererSurfaceChanged = false; - - /* - * This is our main activity thread's loop, we go until - * asked to quit. - */ - - /* - * Update the asynchronous state (window size) - */ - while(true) { // Loop until we're re-created GL context and successfully called swap() - - int w, h; - boolean changed = false; - synchronized (this) { - /* - Runnable r; - while ((r = getEvent()) != null) { - r.run(); - } - */ - if (mPaused) { - mRenderer.onSurfaceDestroyed(); - mEglHelper.finish(); - mNeedStart = true; - if( Globals.NonBlockingSwapBuffers ) - return false; - } - while (needToWait()) { - //Log.v("SDL", "GLSurfaceView_SDL::run(): paused"); - try { - wait(500); - } catch(Exception e) { } - } - if (mDone) { - return false; - } - // changed = mSizeChanged; - w = mWidth; - h = mHeight; - mSizeChanged = false; - mRequestRender = false; - } - if (mNeedStart) { - mEglHelper.start(); - tellRendererSurfaceCreated = true; - changed = true; - mNeedStart = false; - } - if (changed) { - mGL = (GL10) mEglHelper.createSurface(getHolder()); - tellRendererSurfaceChanged = true; - } - if (tellRendererSurfaceCreated) { - //mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig); - tellRendererSurfaceCreated = false; - } - if (tellRendererSurfaceChanged) { - mRenderer.onSurfaceChanged(mGL, w, h); - tellRendererSurfaceChanged = false; - } - /* - * Once we're done with GL, we need to call swapBuffers() - * to instruct the system to display the rendered frame - */ - if( mEglHelper.swap() ) - return true; - // We've lost GL context - recreate it - mRenderer.onSurfaceDestroyed(); - mEglHelper.finish(); - mNeedStart = true; - if( Globals.NonBlockingSwapBuffers ) - return false; - } - } - - private boolean needToWait() { - if (((KeyguardManager)getContext().getSystemService(Context.KEYGUARD_SERVICE)).inKeyguardRestrictedInputMode()) { - return true; // We're in lockscreen - sleep until user unlocks the device - } - - if (mDone) { - return false; - } - - if (mPaused || (! mHasSurface)) { - return true; - } - - if ((mWidth > 0) && (mHeight > 0) && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY))) { - return false; - } - - return true; - } - - public void setRenderMode(int renderMode) { - if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) { - throw new IllegalArgumentException("renderMode"); - } - synchronized(this) { - mRenderMode = renderMode; - if (renderMode == RENDERMODE_CONTINUOUSLY) { - notify(); - } - } - } - - public int getRenderMode() { - synchronized(this) { - return mRenderMode; - } - } - - public void requestRender() { - synchronized(this) { - mRequestRender = true; - notify(); - } - } - - public void surfaceCreated() { - synchronized(this) { - mHasSurface = true; - notify(); - } - } - - public void surfaceDestroyed() { - synchronized(this) { - mHasSurface = false; - notify(); - } - } - - public void onPause() { - Log.v("SDL", "GLSurfaceView_SDL::onPause()"); - synchronized (this) { - mPaused = true; - } - } - - public void onResume() { - Log.v("SDL", "GLSurfaceView_SDL::onResume()"); - synchronized (this) { - mPaused = false; - notify(); - } - } - - public void onWindowResize(int w, int h) { - synchronized (this) { - mWidth = w; - mHeight = h; - mSizeChanged = true; - notify(); - } - } - - public void requestExitAndWait() { - // don't call this from GLThread thread or it is a guaranteed - // deadlock! - synchronized(this) { - mDone = true; - notify(); - } - try { - join(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - - /** - * Queue an "event" to be run on the GL rendering thread. - * @param r the runnable to be run on the GL rendering thread. - */ - public void queueEvent(Runnable r) { - synchronized(this) { - mEventQueue.add(r); - } - } - - private Runnable getEvent() { - synchronized(this) { - if (mEventQueue.size() > 0) { - return mEventQueue.remove(0); - } - - } - return null; - } - - private boolean mDone; - private boolean mPaused; - private boolean mHasSurface; - private int mWidth; - private int mHeight; - private int mRenderMode; - private boolean mRequestRender; - private Renderer mRenderer; - private ArrayList mEventQueue = new ArrayList(); - private EglHelper mEglHelper; - private GL10 mGL = null; - private boolean mNeedStart = false; - } - - static class LogWriter extends Writer { - - @Override public void close() { - flushBuilder(); - } - - @Override public void flush() { - flushBuilder(); - } - - @Override public void write(char[] buf, int offset, int count) { - for(int i = 0; i < count; i++) { - char c = buf[offset + i]; - if ( c == '\n') { - flushBuilder(); - } - else { - mBuilder.append(c); - } - } - } - - private void flushBuilder() { - if (mBuilder.length() > 0) { - Log.v("GLSurfaceView", mBuilder.toString()); - mBuilder.delete(0, mBuilder.length()); - } - } - - private StringBuilder mBuilder = new StringBuilder(); - } - - private static final Semaphore sEglSemaphore = new Semaphore(1); - private boolean mSizeChanged = true; - - private GLThread mGLThread; - private EGLConfigChooser mEGLConfigChooser; - private GLWrapper mGLWrapper; - private int mDebugFlags; -} diff --git a/project/javaSDL2/Globals.java b/project/javaSDL2/Globals.java deleted file mode 100644 index de4323f2a..000000000 --- a/project/javaSDL2/Globals.java +++ /dev/null @@ -1,126 +0,0 @@ -/* -Simple DirectMedia Layer -Java source code (C) 2009-2012 Sergii Pylypenko - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. -*/ - -package net.sourceforge.clonekeenplus; - -/*import android.app.Activity; -import android.content.Context; -import java.util.Vector;*/ -import android.view.KeyEvent; - -class Globals -{ - // These config options are modified by ChangeAppsettings.sh script - see the detailed descriptions there - public static String ApplicationName = "CommanderGenius"; - public static String AppLibraries[] = { "sdl-1.2", }; - public static String AppMainLibraries[] = { "application", "sdl_main" }; - public static final boolean Using_SDL_1_3 = false; - public static final boolean Using_SDL_2_0 = false; - public static String[] DataDownloadUrl = { "Data files are 2 Mb|https://sourceforge.net/projects/libsdl-android/files/CommanderGenius/commandergenius-data.zip/download", "High-quality GFX and music - 40 Mb|https://sourceforge.net/projects/libsdl-android/files/CommanderGenius/commandergenius-hqp.zip/download" }; - public static int VideoDepthBpp = 16; - public static boolean SwVideoMode = false; - public static boolean NeedDepthBuffer = false; - public static boolean NeedStencilBuffer = false; - public static boolean NeedGles2 = false; - public static boolean CompatibilityHacksVideo = false; - public static boolean CompatibilityHacksStaticInit = false; - public static boolean CompatibilityHacksTextInputEmulatesHwKeyboard = false; - public static boolean HorizontalOrientation = true; - public static boolean KeepAspectRatioDefaultSetting = false; - public static boolean InhibitSuspend = false; - public static String ReadmeText = "^You may press \"Home\" now - the data will be downloaded in background".replace("^","\n"); - public static String CommandLine = ""; - public static boolean AppUsesMouse = false; - public static boolean AppNeedsTwoButtonMouse = false; - public static boolean ForceRelativeMouseMode = false; // If both on-screen keyboard and mouse are needed, this will only set the default setting, user may override it later - public static boolean ShowMouseCursor = false; - public static boolean AppNeedsArrowKeys = true; - public static boolean AppNeedsTextInput = true; - public static boolean AppUsesJoystick = false; - public static boolean AppUsesSecondJoystick = false; - public static boolean AppUsesAccelerometer = false; - public static boolean AppUsesGyroscope = false; - public static boolean AppUsesMultitouch = false; - public static boolean NonBlockingSwapBuffers = false; - public static boolean ResetSdlConfigForThisVersion = false; - public static String DeleteFilesOnUpgrade = ""; - public static int AppTouchscreenKeyboardKeysAmount = 4; - public static int AppTouchscreenKeyboardKeysAmountAutoFire = 1; - public static String[] AppTouchscreenKeyboardKeysNames = "Fire Shoot Switch_weapon Jump Run Hide/Seek".split(" "); - public static int StartupMenuButtonTimeout = 3000; - public static int AppMinimumRAM = 0; - public static SettingsMenu.Menu HiddenMenuOptions [] = {}; // If you see error here - update HiddenMenuOptions in your AndroidAppSettings.cfg: change OptionalDownloadConfig to SettingsMenuMisc.OptionalDownloadConfig etc. - public static SettingsMenu.Menu FirstStartMenuOptions [] = { new SettingsMenuMisc.ShowReadme(), (AppUsesMouse && ! ForceRelativeMouseMode ? new SettingsMenuMouse.DisplaySizeConfig() : new SettingsMenu.DummyMenu()), new SettingsMenuMisc.OptionalDownloadConfig(), new SettingsMenuMisc.GyroscopeCalibration() }; - public static String AdmobPublisherId = ""; - public static String AdmobTestDeviceId = ""; - public static String AdmobBannerSize = ""; - - // Phone-specific config, modified by user in "Change phone config" startup dialog, TODO: move this to settings - public static boolean DownloadToSdcard = true; - public static boolean PhoneHasTrackball = false; - public static boolean PhoneHasArrowKeys = false; - public static boolean UseAccelerometerAsArrowKeys = false; - public static boolean UseTouchscreenKeyboard = true; - public static int TouchscreenKeyboardSize = 1; - public static final int TOUCHSCREEN_KEYBOARD_CUSTOM = 4; - public static int TouchscreenKeyboardDrawSize = 1; - public static int TouchscreenKeyboardTheme = 2; - public static int TouchscreenKeyboardTransparency = 2; - public static int AccelerometerSensitivity = 2; - public static int AccelerometerCenterPos = 2; - public static int TrackballDampening = 0; - public static int AudioBufferConfig = 0; - public static boolean OptionalDataDownload[] = null; - public static int LeftClickMethod = Mouse.LEFT_CLICK_NORMAL; - public static int LeftClickKey = KeyEvent.KEYCODE_DPAD_CENTER; - public static int LeftClickTimeout = 3; - public static int RightClickTimeout = 4; - public static int RightClickMethod = AppNeedsTwoButtonMouse ? Mouse.RIGHT_CLICK_WITH_MULTITOUCH : Mouse.RIGHT_CLICK_NONE; - public static int RightClickKey = KeyEvent.KEYCODE_MENU; - public static boolean MoveMouseWithJoystick = false; - public static int MoveMouseWithJoystickSpeed = 0; - public static int MoveMouseWithJoystickAccel = 0; - public static boolean ClickMouseWithDpad = false; - public static boolean RelativeMouseMovement = ForceRelativeMouseMode; // Laptop touchpad mode - public static int RelativeMouseMovementSpeed = 2; - public static int RelativeMouseMovementAccel = 0; - public static int ShowScreenUnderFinger = Mouse.ZOOM_NONE; - public static boolean KeepAspectRatio = KeepAspectRatioDefaultSetting; - public static int ClickScreenPressure = 0; - public static int ClickScreenTouchspotSize = 0; - public static int RemapHwKeycode[] = new int[SDL_Keys.JAVA_KEYCODE_LAST]; - public static int RemapScreenKbKeycode[] = new int[6]; - public static int ScreenKbControlsLayout[][] = AppUsesSecondJoystick ? // Values for 800x480 resolution - new int[][] { { 0, 303, 177, 480 }, { 0, 0, 48, 48 }, { 400, 392, 488, 480 }, { 312, 392, 400, 480 }, { 400, 304, 488, 392 }, { 312, 304, 400, 392 }, { 400, 216, 488, 304 }, { 312, 216, 400, 304 }, { 623, 303, 800, 480 } } : - new int[][] { { 0, 303, 177, 480 }, { 0, 0, 48, 48 }, { 712, 392, 800, 480 }, { 624, 392, 712, 480 }, { 712, 304, 800, 392 }, { 624, 304, 712, 392 }, { 712, 216, 800, 304 }, { 624, 216, 712, 304 } }; - public static boolean ScreenKbControlsShown[] = new boolean[ScreenKbControlsLayout.length]; // Also joystick and text input button added - public static int RemapMultitouchGestureKeycode[] = new int[4]; - public static boolean MultitouchGesturesUsed[] = new boolean[4]; - public static int MultitouchGestureSensitivity = 1; - public static int TouchscreenCalibration[] = new int[4]; - public static String DataDir = new String(""); - public static boolean VideoLinearFilter = true; - public static boolean MultiThreadedVideo = false; - public static boolean BrokenLibCMessageShown = false; - // Gyroscope calibration - public static float gyro_x1, gyro_x2, gyro_xc, gyro_y1, gyro_y2, gyro_yc, gyro_z1, gyro_z2, gyro_zc; - public static boolean OuyaEmulation = false; // For debugging -} diff --git a/project/javaSDL2/HIDDevice.java b/project/javaSDL2/HIDDevice.java new file mode 100644 index 000000000..955df5d14 --- /dev/null +++ b/project/javaSDL2/HIDDevice.java @@ -0,0 +1,22 @@ +package org.libsdl.app; + +import android.hardware.usb.UsbDevice; + +interface HIDDevice +{ + public int getId(); + public int getVendorId(); + public int getProductId(); + public String getSerialNumber(); + public int getVersion(); + public String getManufacturerName(); + public String getProductName(); + public UsbDevice getDevice(); + public boolean open(); + public int sendFeatureReport(byte[] report); + public int sendOutputReport(byte[] report); + public boolean getFeatureReport(byte[] report); + public void setFrozen(boolean frozen); + public void close(); + public void shutdown(); +} diff --git a/project/javaSDL2/HIDDeviceBLESteamController.java b/project/javaSDL2/HIDDeviceBLESteamController.java new file mode 100644 index 000000000..94a28189b --- /dev/null +++ b/project/javaSDL2/HIDDeviceBLESteamController.java @@ -0,0 +1,650 @@ +package org.libsdl.app; + +import android.content.Context; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothGattService; +import android.hardware.usb.UsbDevice; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.os.*; + +//import com.android.internal.util.HexDump; + +import java.lang.Runnable; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.UUID; + +class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice { + + private static final String TAG = "hidapi"; + private HIDDeviceManager mManager; + private BluetoothDevice mDevice; + private int mDeviceId; + private BluetoothGatt mGatt; + private boolean mIsRegistered = false; + private boolean mIsConnected = false; + private boolean mIsChromebook = false; + private boolean mIsReconnecting = false; + private boolean mFrozen = false; + private LinkedList mOperations; + GattOperation mCurrentOperation = null; + private Handler mHandler; + + private static final int TRANSPORT_AUTO = 0; + private static final int TRANSPORT_BREDR = 1; + private static final int TRANSPORT_LE = 2; + + private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000; + + static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3"); + static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3"); + static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3"); + static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 }; + + static class GattOperation { + private enum Operation { + CHR_READ, + CHR_WRITE, + ENABLE_NOTIFICATION + } + + Operation mOp; + UUID mUuid; + byte[] mValue; + BluetoothGatt mGatt; + boolean mResult = true; + + private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) { + mGatt = gatt; + mOp = operation; + mUuid = uuid; + } + + private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) { + mGatt = gatt; + mOp = operation; + mUuid = uuid; + mValue = value; + } + + public void run() { + // This is executed in main thread + BluetoothGattCharacteristic chr; + + switch (mOp) { + case CHR_READ: + chr = getCharacteristic(mUuid); + //Log.v(TAG, "Reading characteristic " + chr.getUuid()); + if (!mGatt.readCharacteristic(chr)) { + Log.e(TAG, "Unable to read characteristic " + mUuid.toString()); + mResult = false; + break; + } + mResult = true; + break; + case CHR_WRITE: + chr = getCharacteristic(mUuid); + //Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value)); + chr.setValue(mValue); + if (!mGatt.writeCharacteristic(chr)) { + Log.e(TAG, "Unable to write characteristic " + mUuid.toString()); + mResult = false; + break; + } + mResult = true; + break; + case ENABLE_NOTIFICATION: + chr = getCharacteristic(mUuid); + //Log.v(TAG, "Writing descriptor of " + chr.getUuid()); + if (chr != null) { + BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); + if (cccd != null) { + int properties = chr.getProperties(); + byte[] value; + if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { + value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; + } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) { + value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE; + } else { + Log.e(TAG, "Unable to start notifications on input characteristic"); + mResult = false; + return; + } + + mGatt.setCharacteristicNotification(chr, true); + cccd.setValue(value); + if (!mGatt.writeDescriptor(cccd)) { + Log.e(TAG, "Unable to write descriptor " + mUuid.toString()); + mResult = false; + return; + } + mResult = true; + } + } + } + } + + public boolean finish() { + return mResult; + } + + private BluetoothGattCharacteristic getCharacteristic(UUID uuid) { + BluetoothGattService valveService = mGatt.getService(steamControllerService); + if (valveService == null) + return null; + return valveService.getCharacteristic(uuid); + } + + static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) { + return new GattOperation(gatt, Operation.CHR_READ, uuid); + } + + static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) { + return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value); + } + + static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) { + return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid); + } + } + + public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) { + mManager = manager; + mDevice = device; + mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier()); + mIsRegistered = false; + mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); + mOperations = new LinkedList(); + mHandler = new Handler(Looper.getMainLooper()); + + mGatt = connectGatt(); + // final HIDDeviceBLESteamController finalThis = this; + // mHandler.postDelayed(new Runnable() { + // @Override + // public void run() { + // finalThis.checkConnectionForChromebookIssue(); + // } + // }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); + } + + public String getIdentifier() { + return String.format("SteamController.%s", mDevice.getAddress()); + } + + public BluetoothGatt getGatt() { + return mGatt; + } + + // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead + // of TRANSPORT_LE. Let's force ourselves to connect low energy. + private BluetoothGatt connectGatt(boolean managed) { + if (Build.VERSION.SDK_INT >= 23) { + try { + return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE); + } catch (Exception e) { + return mDevice.connectGatt(mManager.getContext(), managed, this); + } + } else { + return mDevice.connectGatt(mManager.getContext(), managed, this); + } + } + + private BluetoothGatt connectGatt() { + return connectGatt(false); + } + + protected int getConnectionState() { + + Context context = mManager.getContext(); + if (context == null) { + // We are lacking any context to get our Bluetooth information. We'll just assume disconnected. + return BluetoothProfile.STATE_DISCONNECTED; + } + + BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE); + if (btManager == null) { + // This device doesn't support Bluetooth. We should never be here, because how did + // we instantiate a device to start with? + return BluetoothProfile.STATE_DISCONNECTED; + } + + return btManager.getConnectionState(mDevice, BluetoothProfile.GATT); + } + + public void reconnect() { + + if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + mGatt.disconnect(); + mGatt = connectGatt(); + } + + } + + protected void checkConnectionForChromebookIssue() { + if (!mIsChromebook) { + // We only do this on Chromebooks, because otherwise it's really annoying to just attempt + // over and over. + return; + } + + int connectionState = getConnectionState(); + + switch (connectionState) { + case BluetoothProfile.STATE_CONNECTED: + if (!mIsConnected) { + // We are in the Bad Chromebook Place. We can force a disconnect + // to try to recover. + Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect."); + mIsReconnecting = true; + mGatt.disconnect(); + mGatt = connectGatt(false); + break; + } + else if (!isRegistered()) { + if (mGatt.getServices().size() > 0) { + Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover."); + probeService(this); + } + else { + Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover."); + mIsReconnecting = true; + mGatt.disconnect(); + mGatt = connectGatt(false); + break; + } + } + else { + Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!"); + return; + } + break; + + case BluetoothProfile.STATE_DISCONNECTED: + Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover."); + + mIsReconnecting = true; + mGatt.disconnect(); + mGatt = connectGatt(false); + break; + + case BluetoothProfile.STATE_CONNECTING: + Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer."); + break; + } + + final HIDDeviceBLESteamController finalThis = this; + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + finalThis.checkConnectionForChromebookIssue(); + } + }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); + } + + private boolean isRegistered() { + return mIsRegistered; + } + + private void setRegistered() { + mIsRegistered = true; + } + + private boolean probeService(HIDDeviceBLESteamController controller) { + + if (isRegistered()) { + return true; + } + + if (!mIsConnected) { + return false; + } + + Log.v(TAG, "probeService controller=" + controller); + + for (BluetoothGattService service : mGatt.getServices()) { + if (service.getUuid().equals(steamControllerService)) { + Log.v(TAG, "Found Valve steam controller service " + service.getUuid()); + + for (BluetoothGattCharacteristic chr : service.getCharacteristics()) { + if (chr.getUuid().equals(inputCharacteristic)) { + Log.v(TAG, "Found input characteristic"); + // Start notifications + BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); + if (cccd != null) { + enableNotification(chr.getUuid()); + } + } + } + return true; + } + } + + if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) { + Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us."); + mIsConnected = false; + mIsReconnecting = true; + mGatt.disconnect(); + mGatt = connectGatt(false); + } + + return false; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + private void finishCurrentGattOperation() { + GattOperation op = null; + synchronized (mOperations) { + if (mCurrentOperation != null) { + op = mCurrentOperation; + mCurrentOperation = null; + } + } + if (op != null) { + boolean result = op.finish(); // TODO: Maybe in main thread as well? + + // Our operation failed, let's add it back to the beginning of our queue. + if (!result) { + mOperations.addFirst(op); + } + } + executeNextGattOperation(); + } + + private void executeNextGattOperation() { + synchronized (mOperations) { + if (mCurrentOperation != null) + return; + + if (mOperations.isEmpty()) + return; + + mCurrentOperation = mOperations.removeFirst(); + } + + // Run in main thread + mHandler.post(new Runnable() { + @Override + public void run() { + synchronized (mOperations) { + if (mCurrentOperation == null) { + Log.e(TAG, "Current operation null in executor?"); + return; + } + + mCurrentOperation.run(); + // now wait for the GATT callback and when it comes, finish this operation + } + } + }); + } + + private void queueGattOperation(GattOperation op) { + synchronized (mOperations) { + mOperations.add(op); + } + executeNextGattOperation(); + } + + private void enableNotification(UUID chrUuid) { + GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid); + queueGattOperation(op); + } + + public void writeCharacteristic(UUID uuid, byte[] value) { + GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value); + queueGattOperation(op); + } + + public void readCharacteristic(UUID uuid) { + GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid); + queueGattOperation(op); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////// BluetoothGattCallback overridden methods + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + public void onConnectionStateChange(BluetoothGatt g, int status, int newState) { + //Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState); + mIsReconnecting = false; + if (newState == 2) { + mIsConnected = true; + // Run directly, without GattOperation + if (!isRegistered()) { + mHandler.post(new Runnable() { + @Override + public void run() { + mGatt.discoverServices(); + } + }); + } + } + else if (newState == 0) { + mIsConnected = false; + } + + // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent. + } + + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + //Log.v(TAG, "onServicesDiscovered status=" + status); + if (status == 0) { + if (gatt.getServices().size() == 0) { + Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack."); + mIsReconnecting = true; + mIsConnected = false; + gatt.disconnect(); + mGatt = connectGatt(false); + } + else { + probeService(this); + } + } + } + + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + //Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid()); + + if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) { + mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue()); + } + + finishCurrentGattOperation(); + } + + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + //Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid()); + + if (characteristic.getUuid().equals(reportCharacteristic)) { + // Only register controller with the native side once it has been fully configured + if (!isRegistered()) { + Log.v(TAG, "Registering Steam Controller with ID: " + getId()); + mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0); + setRegistered(); + } + } + + finishCurrentGattOperation(); + } + + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + // Enable this for verbose logging of controller input reports + //Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue())); + + if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) { + mManager.HIDDeviceInputReport(getId(), characteristic.getValue()); + } + } + + public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + //Log.v(TAG, "onDescriptorRead status=" + status); + } + + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + BluetoothGattCharacteristic chr = descriptor.getCharacteristic(); + //Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid()); + + if (chr.getUuid().equals(inputCharacteristic)) { + boolean hasWrittenInputDescriptor = true; + BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic); + if (reportChr != null) { + Log.v(TAG, "Writing report characteristic to enter valve mode"); + reportChr.setValue(enterValveMode); + gatt.writeCharacteristic(reportChr); + } + } + + finishCurrentGattOperation(); + } + + public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { + //Log.v(TAG, "onReliableWriteCompleted status=" + status); + } + + public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { + //Log.v(TAG, "onReadRemoteRssi status=" + status); + } + + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { + //Log.v(TAG, "onMtuChanged status=" + status); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + //////// Public API + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public int getId() { + return mDeviceId; + } + + @Override + public int getVendorId() { + // Valve Corporation + final int VALVE_USB_VID = 0x28DE; + return VALVE_USB_VID; + } + + @Override + public int getProductId() { + // We don't have an easy way to query from the Bluetooth device, but we know what it is + final int D0G_BLE2_PID = 0x1106; + return D0G_BLE2_PID; + } + + @Override + public String getSerialNumber() { + // This will be read later via feature report by Steam + return "12345"; + } + + @Override + public int getVersion() { + return 0; + } + + @Override + public String getManufacturerName() { + return "Valve Corporation"; + } + + @Override + public String getProductName() { + return "Steam Controller"; + } + + @Override + public UsbDevice getDevice() { + return null; + } + + @Override + public boolean open() { + return true; + } + + @Override + public int sendFeatureReport(byte[] report) { + if (!isRegistered()) { + Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!"); + if (mIsConnected) { + probeService(this); + } + return -1; + } + + // We need to skip the first byte, as that doesn't go over the air + byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1); + //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report)); + writeCharacteristic(reportCharacteristic, actual_report); + return report.length; + } + + @Override + public int sendOutputReport(byte[] report) { + if (!isRegistered()) { + Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!"); + if (mIsConnected) { + probeService(this); + } + return -1; + } + + //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report)); + writeCharacteristic(reportCharacteristic, report); + return report.length; + } + + @Override + public boolean getFeatureReport(byte[] report) { + if (!isRegistered()) { + Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!"); + if (mIsConnected) { + probeService(this); + } + return false; + } + + //Log.v(TAG, "getFeatureReport"); + readCharacteristic(reportCharacteristic); + return true; + } + + @Override + public void close() { + } + + @Override + public void setFrozen(boolean frozen) { + mFrozen = frozen; + } + + @Override + public void shutdown() { + close(); + + BluetoothGatt g = mGatt; + if (g != null) { + g.disconnect(); + g.close(); + mGatt = null; + } + mManager = null; + mIsRegistered = false; + mIsConnected = false; + mOperations.clear(); + } + +} + diff --git a/project/javaSDL2/HIDDeviceManager.java b/project/javaSDL2/HIDDeviceManager.java new file mode 100644 index 000000000..56f677e66 --- /dev/null +++ b/project/javaSDL2/HIDDeviceManager.java @@ -0,0 +1,669 @@ +package org.libsdl.app; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.util.Log; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.hardware.usb.*; +import android.os.Handler; +import android.os.Looper; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +public class HIDDeviceManager { + private static final String TAG = "hidapi"; + private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION"; + + private static HIDDeviceManager sManager; + private static int sManagerRefCount = 0; + + public static HIDDeviceManager acquire(Context context) { + if (sManagerRefCount == 0) { + sManager = new HIDDeviceManager(context); + } + ++sManagerRefCount; + return sManager; + } + + public static void release(HIDDeviceManager manager) { + if (manager == sManager) { + --sManagerRefCount; + if (sManagerRefCount == 0) { + sManager.close(); + sManager = null; + } + } + } + + private Context mContext; + private HashMap mDevicesById = new HashMap(); + private HashMap mBluetoothDevices = new HashMap(); + private int mNextDeviceId = 0; + private SharedPreferences mSharedPreferences = null; + private boolean mIsChromebook = false; + private UsbManager mUsbManager; + private Handler mHandler; + private BluetoothManager mBluetoothManager; + private List mLastBluetoothDevices; + + private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + handleUsbDeviceAttached(usbDevice); + } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + handleUsbDeviceDetached(usbDevice); + } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)); + } + } + }; + + private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + // Bluetooth device was connected. If it was a Steam Controller, handle it + if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Log.d(TAG, "Bluetooth device connected: " + device); + + if (isSteamController(device)) { + connectBluetoothDevice(device); + } + } + + // Bluetooth device was disconnected, remove from controller manager (if any) + if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Log.d(TAG, "Bluetooth device disconnected: " + device); + + disconnectBluetoothDevice(device); + } + } + }; + + private HIDDeviceManager(final Context context) { + mContext = context; + + // Make sure we have the HIDAPI library loaded with the native functions + try { + SDL.loadLibrary("hidapi"); + } catch (Throwable e) { + Log.w(TAG, "Couldn't load hidapi: " + e.toString()); + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setCancelable(false); + builder.setTitle("SDL HIDAPI Error"); + builder.setMessage("Please report the following error to the SDL maintainers: " + e.getMessage()); + builder.setNegativeButton("Quit", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + try { + // If our context is an activity, exit rather than crashing when we can't + // call our native functions. + Activity activity = (Activity)context; + + activity.finish(); + } + catch (ClassCastException cce) { + // Context wasn't an activity, there's nothing we can do. Give up and return. + } + } + }); + builder.show(); + + return; + } + + HIDDeviceRegisterCallback(); + + mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE); + mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); + +// if (shouldClear) { +// SharedPreferences.Editor spedit = mSharedPreferences.edit(); +// spedit.clear(); +// spedit.commit(); +// } +// else + { + mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0); + } + + initializeUSB(); + initializeBluetooth(); + } + + public Context getContext() { + return mContext; + } + + public int getDeviceIDForIdentifier(String identifier) { + SharedPreferences.Editor spedit = mSharedPreferences.edit(); + + int result = mSharedPreferences.getInt(identifier, 0); + if (result == 0) { + result = mNextDeviceId++; + spedit.putInt("next_device_id", mNextDeviceId); + } + + spedit.putInt(identifier, result); + spedit.commit(); + return result; + } + + private void initializeUSB() { + mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE); + + /* + // Logging + for (UsbDevice device : mUsbManager.getDeviceList().values()) { + Log.i(TAG,"Path: " + device.getDeviceName()); + Log.i(TAG,"Manufacturer: " + device.getManufacturerName()); + Log.i(TAG,"Product: " + device.getProductName()); + Log.i(TAG,"ID: " + device.getDeviceId()); + Log.i(TAG,"Class: " + device.getDeviceClass()); + Log.i(TAG,"Protocol: " + device.getDeviceProtocol()); + Log.i(TAG,"Vendor ID " + device.getVendorId()); + Log.i(TAG,"Product ID: " + device.getProductId()); + Log.i(TAG,"Interface count: " + device.getInterfaceCount()); + Log.i(TAG,"---------------------------------------"); + + // Get interface details + for (int index = 0; index < device.getInterfaceCount(); index++) { + UsbInterface mUsbInterface = device.getInterface(index); + Log.i(TAG," ***** *****"); + Log.i(TAG," Interface index: " + index); + Log.i(TAG," Interface ID: " + mUsbInterface.getId()); + Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass()); + Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass()); + Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol()); + Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount()); + + // Get endpoint details + for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++) + { + UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi); + Log.i(TAG," ++++ ++++ ++++"); + Log.i(TAG," Endpoint index: " + epi); + Log.i(TAG," Attributes: " + mEndpoint.getAttributes()); + Log.i(TAG," Direction: " + mEndpoint.getDirection()); + Log.i(TAG," Number: " + mEndpoint.getEndpointNumber()); + Log.i(TAG," Interval: " + mEndpoint.getInterval()); + Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize()); + Log.i(TAG," Type: " + mEndpoint.getType()); + } + } + } + Log.i(TAG," No more devices connected."); + */ + + // Register for USB broadcasts and permission completions + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION); + mContext.registerReceiver(mUsbBroadcast, filter); + + for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) { + handleUsbDeviceAttached(usbDevice); + } + } + + UsbManager getUSBManager() { + return mUsbManager; + } + + private void shutdownUSB() { + try { + mContext.unregisterReceiver(mUsbBroadcast); + } catch (Exception e) { + // We may not have registered, that's okay + } + } + + private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) { + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) { + return true; + } + if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) { + return true; + } + return false; + } + + private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) { + final int XB360_IFACE_SUBCLASS = 93; + final int XB360_IFACE_PROTOCOL = 1; // Wired + final int XB360W_IFACE_PROTOCOL = 129; // Wireless + final int[] SUPPORTED_VENDORS = { + 0x0079, // GPD Win 2 + 0x044f, // Thrustmaster + 0x045e, // Microsoft + 0x046d, // Logitech + 0x056e, // Elecom + 0x06a3, // Saitek + 0x0738, // Mad Catz + 0x07ff, // Mad Catz + 0x0e6f, // PDP + 0x0f0d, // Hori + 0x1038, // SteelSeries + 0x11c9, // Nacon + 0x12ab, // Unknown + 0x1430, // RedOctane + 0x146b, // BigBen + 0x1532, // Razer Sabertooth + 0x15e4, // Numark + 0x162e, // Joytech + 0x1689, // Razer Onza + 0x1bad, // Harmonix + 0x24c6, // PowerA + }; + + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && + usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS && + (usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL || + usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) { + int vendor_id = usbDevice.getVendorId(); + for (int supportedVid : SUPPORTED_VENDORS) { + if (vendor_id == supportedVid) { + return true; + } + } + } + return false; + } + + private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) { + final int XB1_IFACE_SUBCLASS = 71; + final int XB1_IFACE_PROTOCOL = 208; + final int[] SUPPORTED_VENDORS = { + 0x045e, // Microsoft + 0x0738, // Mad Catz + 0x0e6f, // PDP + 0x0f0d, // Hori + 0x1532, // Razer Wildcat + 0x24c6, // PowerA + 0x2e24, // Hyperkin + }; + + if (usbInterface.getId() == 0 && + usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && + usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS && + usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) { + int vendor_id = usbDevice.getVendorId(); + for (int supportedVid : SUPPORTED_VENDORS) { + if (vendor_id == supportedVid) { + return true; + } + } + } + return false; + } + + private void handleUsbDeviceAttached(UsbDevice usbDevice) { + connectHIDDeviceUSB(usbDevice); + } + + private void handleUsbDeviceDetached(UsbDevice usbDevice) { + List devices = new ArrayList(); + for (HIDDevice device : mDevicesById.values()) { + if (usbDevice.equals(device.getDevice())) { + devices.add(device.getId()); + } + } + for (int id : devices) { + HIDDevice device = mDevicesById.get(id); + mDevicesById.remove(id); + device.shutdown(); + HIDDeviceDisconnected(id); + } + } + + private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) { + for (HIDDevice device : mDevicesById.values()) { + if (usbDevice.equals(device.getDevice())) { + boolean opened = false; + if (permission_granted) { + opened = device.open(); + } + HIDDeviceOpenResult(device.getId(), opened); + } + } + } + + private void connectHIDDeviceUSB(UsbDevice usbDevice) { + synchronized (this) { + for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) { + UsbInterface usbInterface = usbDevice.getInterface(interface_index); + if (isHIDDeviceInterface(usbDevice, usbInterface)) { + HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index); + int id = device.getId(); + mDevicesById.put(id, device); + HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol()); + } + } + } + } + + private void initializeBluetooth() { + Log.d(TAG, "Initializing Bluetooth"); + + if (mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); + return; + } + + // Find bonded bluetooth controllers and create SteamControllers for them + mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE); + if (mBluetoothManager == null) { + // This device doesn't support Bluetooth. + return; + } + + BluetoothAdapter btAdapter = mBluetoothManager.getAdapter(); + if (btAdapter == null) { + // This device has Bluetooth support in the codebase, but has no available adapters. + return; + } + + // Get our bonded devices. + for (BluetoothDevice device : btAdapter.getBondedDevices()) { + + Log.d(TAG, "Bluetooth device available: " + device); + if (isSteamController(device)) { + connectBluetoothDevice(device); + } + + } + + // NOTE: These don't work on Chromebooks, to my undying dismay. + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); + filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); + mContext.registerReceiver(mBluetoothBroadcast, filter); + + if (mIsChromebook) { + mHandler = new Handler(Looper.getMainLooper()); + mLastBluetoothDevices = new ArrayList(); + + // final HIDDeviceManager finalThis = this; + // mHandler.postDelayed(new Runnable() { + // @Override + // public void run() { + // finalThis.chromebookConnectionHandler(); + // } + // }, 5000); + } + } + + private void shutdownBluetooth() { + try { + mContext.unregisterReceiver(mBluetoothBroadcast); + } catch (Exception e) { + // We may not have registered, that's okay + } + } + + // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly. + // This function provides a sort of dummy version of that, watching for changes in the + // connected devices and attempting to add controllers as things change. + public void chromebookConnectionHandler() { + if (!mIsChromebook) { + return; + } + + ArrayList disconnected = new ArrayList(); + ArrayList connected = new ArrayList(); + + List currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT); + + for (BluetoothDevice bluetoothDevice : currentConnected) { + if (!mLastBluetoothDevices.contains(bluetoothDevice)) { + connected.add(bluetoothDevice); + } + } + for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) { + if (!currentConnected.contains(bluetoothDevice)) { + disconnected.add(bluetoothDevice); + } + } + + mLastBluetoothDevices = currentConnected; + + for (BluetoothDevice bluetoothDevice : disconnected) { + disconnectBluetoothDevice(bluetoothDevice); + } + for (BluetoothDevice bluetoothDevice : connected) { + connectBluetoothDevice(bluetoothDevice); + } + + final HIDDeviceManager finalThis = this; + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + finalThis.chromebookConnectionHandler(); + } + }, 10000); + } + + public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) { + Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice); + synchronized (this) { + if (mBluetoothDevices.containsKey(bluetoothDevice)) { + Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect"); + + HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); + device.reconnect(); + + return false; + } + HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice); + int id = device.getId(); + mBluetoothDevices.put(bluetoothDevice, device); + mDevicesById.put(id, device); + + // The Steam Controller will mark itself connected once initialization is complete + } + return true; + } + + public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) { + synchronized (this) { + HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); + if (device == null) + return; + + int id = device.getId(); + mBluetoothDevices.remove(bluetoothDevice); + mDevicesById.remove(id); + device.shutdown(); + HIDDeviceDisconnected(id); + } + } + + public boolean isSteamController(BluetoothDevice bluetoothDevice) { + // Sanity check. If you pass in a null device, by definition it is never a Steam Controller. + if (bluetoothDevice == null) { + return false; + } + + // If the device has no local name, we really don't want to try an equality check against it. + if (bluetoothDevice.getName() == null) { + return false; + } + + return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0); + } + + private void close() { + shutdownUSB(); + shutdownBluetooth(); + synchronized (this) { + for (HIDDevice device : mDevicesById.values()) { + device.shutdown(); + } + mDevicesById.clear(); + mBluetoothDevices.clear(); + HIDDeviceReleaseCallback(); + } + } + + public void setFrozen(boolean frozen) { + synchronized (this) { + for (HIDDevice device : mDevicesById.values()) { + device.setFrozen(frozen); + } + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + private HIDDevice getDevice(int id) { + synchronized (this) { + HIDDevice result = mDevicesById.get(id); + if (result == null) { + Log.v(TAG, "No device for id: " + id); + Log.v(TAG, "Available devices: " + mDevicesById.keySet()); + } + return result; + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////// JNI interface functions + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + public boolean openDevice(int deviceID) { + Log.v(TAG, "openDevice deviceID=" + deviceID); + HIDDevice device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return false; + } + + // Look to see if this is a USB device and we have permission to access it + UsbDevice usbDevice = device.getDevice(); + if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) { + HIDDeviceOpenPending(deviceID); + try { + mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), 0)); + } catch (Exception e) { + Log.v(TAG, "Couldn't request permission for USB device " + usbDevice); + HIDDeviceOpenResult(deviceID, false); + } + return false; + } + + try { + return device.open(); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + return false; + } + + public int sendOutputReport(int deviceID, byte[] report) { + try { + //Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length); + HIDDevice device; + device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return -1; + } + + return device.sendOutputReport(report); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + return -1; + } + + public int sendFeatureReport(int deviceID, byte[] report) { + try { + //Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length); + HIDDevice device; + device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return -1; + } + + return device.sendFeatureReport(report); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + return -1; + } + + public boolean getFeatureReport(int deviceID, byte[] report) { + try { + //Log.v(TAG, "getFeatureReport deviceID=" + deviceID); + HIDDevice device; + device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return false; + } + + return device.getFeatureReport(report); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + return false; + } + + public void closeDevice(int deviceID) { + try { + Log.v(TAG, "closeDevice deviceID=" + deviceID); + HIDDevice device; + device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return; + } + + device.close(); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + } + + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////// Native methods + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + private native void HIDDeviceRegisterCallback(); + private native void HIDDeviceReleaseCallback(); + + native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol); + native void HIDDeviceOpenPending(int deviceID); + native void HIDDeviceOpenResult(int deviceID, boolean opened); + native void HIDDeviceDisconnected(int deviceID); + + native void HIDDeviceInputReport(int deviceID, byte[] report); + native void HIDDeviceFeatureReport(int deviceID, byte[] report); +} diff --git a/project/javaSDL2/HIDDeviceUSB.java b/project/javaSDL2/HIDDeviceUSB.java new file mode 100644 index 000000000..33816e344 --- /dev/null +++ b/project/javaSDL2/HIDDeviceUSB.java @@ -0,0 +1,304 @@ +package org.libsdl.app; + +import android.hardware.usb.*; +import android.os.Build; +import android.util.Log; +import java.util.Arrays; + +class HIDDeviceUSB implements HIDDevice { + + private static final String TAG = "hidapi"; + + protected HIDDeviceManager mManager; + protected UsbDevice mDevice; + protected int mInterfaceIndex; + protected int mInterface; + protected int mDeviceId; + protected UsbDeviceConnection mConnection; + protected UsbEndpoint mInputEndpoint; + protected UsbEndpoint mOutputEndpoint; + protected InputThread mInputThread; + protected boolean mRunning; + protected boolean mFrozen; + + public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) { + mManager = manager; + mDevice = usbDevice; + mInterfaceIndex = interface_index; + mInterface = mDevice.getInterface(mInterfaceIndex).getId(); + mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier()); + mRunning = false; + } + + public String getIdentifier() { + return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex); + } + + @Override + public int getId() { + return mDeviceId; + } + + @Override + public int getVendorId() { + return mDevice.getVendorId(); + } + + @Override + public int getProductId() { + return mDevice.getProductId(); + } + + @Override + public String getSerialNumber() { + String result = null; + if (Build.VERSION.SDK_INT >= 21) { + result = mDevice.getSerialNumber(); + } + if (result == null) { + result = ""; + } + return result; + } + + @Override + public int getVersion() { + return 0; + } + + @Override + public String getManufacturerName() { + String result = null; + if (Build.VERSION.SDK_INT >= 21) { + result = mDevice.getManufacturerName(); + } + if (result == null) { + result = String.format("%x", getVendorId()); + } + return result; + } + + @Override + public String getProductName() { + String result = null; + if (Build.VERSION.SDK_INT >= 21) { + result = mDevice.getProductName(); + } + if (result == null) { + result = String.format("%x", getProductId()); + } + return result; + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + public String getDeviceName() { + return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")"; + } + + @Override + public boolean open() { + mConnection = mManager.getUSBManager().openDevice(mDevice); + if (mConnection == null) { + Log.w(TAG, "Unable to open USB device " + getDeviceName()); + return false; + } + + // Force claim our interface + UsbInterface iface = mDevice.getInterface(mInterfaceIndex); + if (!mConnection.claimInterface(iface, true)) { + Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName()); + close(); + return false; + } + + // Find the endpoints + for (int j = 0; j < iface.getEndpointCount(); j++) { + UsbEndpoint endpt = iface.getEndpoint(j); + switch (endpt.getDirection()) { + case UsbConstants.USB_DIR_IN: + if (mInputEndpoint == null) { + mInputEndpoint = endpt; + } + break; + case UsbConstants.USB_DIR_OUT: + if (mOutputEndpoint == null) { + mOutputEndpoint = endpt; + } + break; + } + } + + // Make sure the required endpoints were present + if (mInputEndpoint == null || mOutputEndpoint == null) { + Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName()); + close(); + return false; + } + + // Start listening for input + mRunning = true; + mInputThread = new InputThread(); + mInputThread.start(); + + return true; + } + + @Override + public int sendFeatureReport(byte[] report) { + int res = -1; + int offset = 0; + int length = report.length; + boolean skipped_report_id = false; + byte report_number = report[0]; + + if (report_number == 0x0) { + ++offset; + --length; + skipped_report_id = true; + } + + res = mConnection.controlTransfer( + UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT, + 0x09/*HID set_report*/, + (3/*HID feature*/ << 8) | report_number, + mInterface, + report, offset, length, + 1000/*timeout millis*/); + + if (res < 0) { + Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName()); + return -1; + } + + if (skipped_report_id) { + ++length; + } + return length; + } + + @Override + public int sendOutputReport(byte[] report) { + int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000); + if (r != report.length) { + Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName()); + } + return r; + } + + @Override + public boolean getFeatureReport(byte[] report) { + int res = -1; + int offset = 0; + int length = report.length; + boolean skipped_report_id = false; + byte report_number = report[0]; + + if (report_number == 0x0) { + /* Offset the return buffer by 1, so that the report ID + will remain in byte 0. */ + ++offset; + --length; + skipped_report_id = true; + } + + res = mConnection.controlTransfer( + UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN, + 0x01/*HID get_report*/, + (3/*HID feature*/ << 8) | report_number, + mInterface, + report, offset, length, + 1000/*timeout millis*/); + + if (res < 0) { + Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName()); + return false; + } + + if (skipped_report_id) { + ++res; + ++length; + } + + byte[] data; + if (res == length) { + data = report; + } else { + data = Arrays.copyOfRange(report, 0, res); + } + mManager.HIDDeviceFeatureReport(mDeviceId, data); + + return true; + } + + @Override + public void close() { + mRunning = false; + if (mInputThread != null) { + while (mInputThread.isAlive()) { + mInputThread.interrupt(); + try { + mInputThread.join(); + } catch (InterruptedException e) { + // Keep trying until we're done + } + } + mInputThread = null; + } + if (mConnection != null) { + UsbInterface iface = mDevice.getInterface(mInterfaceIndex); + mConnection.releaseInterface(iface); + mConnection.close(); + mConnection = null; + } + } + + @Override + public void shutdown() { + close(); + mManager = null; + } + + @Override + public void setFrozen(boolean frozen) { + mFrozen = frozen; + } + + protected class InputThread extends Thread { + @Override + public void run() { + int packetSize = mInputEndpoint.getMaxPacketSize(); + byte[] packet = new byte[packetSize]; + while (mRunning) { + int r; + try + { + r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000); + } + catch (Exception e) + { + Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e); + break; + } + if (r < 0) { + // Could be a timeout or an I/O error + } + if (r > 0) { + byte[] data; + if (r == packetSize) { + data = packet; + } else { + data = Arrays.copyOfRange(packet, 0, r); + } + + if (!mFrozen) { + mManager.HIDDeviceInputReport(mDeviceId, data); + } + } + } + } + } +} diff --git a/project/javaSDL2/Keycodes.java b/project/javaSDL2/Keycodes.java deleted file mode 100644 index 15aa6b6e9..000000000 --- a/project/javaSDL2/Keycodes.java +++ /dev/null @@ -1,592 +0,0 @@ -/* -Simple DirectMedia Layer -Java source code (C) 2009-2012 Sergii Pylypenko - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. -*/ - -package net.sourceforge.clonekeenplus; - -import java.lang.String; -import java.util.ArrayList; -import java.util.Arrays; -import java.lang.reflect.Field; - - -// Autogenerated by hand with a command: -// grep 'SDLK_' SDL_keysym.h | sed 's/SDLK_\([a-zA-Z0-9_]\+\).*[=] \([0-9]\+\).*/public static final int SDLK_\1 = \2;/' >> Keycodes.java -class SDL_1_2_Keycodes -{ - public static final int SDLK_UNKNOWN = 0; - public static final int SDLK_BACKSPACE = 8; - public static final int SDLK_TAB = 9; - public static final int SDLK_CLEAR = 12; - public static final int SDLK_RETURN = 13; - public static final int SDLK_PAUSE = 19; - public static final int SDLK_ESCAPE = 27; - public static final int SDLK_SPACE = 32; - public static final int SDLK_EXCLAIM = 33; - public static final int SDLK_QUOTEDBL = 34; - public static final int SDLK_HASH = 35; - public static final int SDLK_DOLLAR = 36; - public static final int SDLK_AMPERSAND = 38; - public static final int SDLK_QUOTE = 39; - public static final int SDLK_LEFTPAREN = 40; - public static final int SDLK_RIGHTPAREN = 41; - public static final int SDLK_ASTERISK = 42; - public static final int SDLK_PLUS = 43; - public static final int SDLK_COMMA = 44; - public static final int SDLK_MINUS = 45; - public static final int SDLK_PERIOD = 46; - public static final int SDLK_SLASH = 47; - public static final int SDLK_0 = 48; - public static final int SDLK_1 = 49; - public static final int SDLK_2 = 50; - public static final int SDLK_3 = 51; - public static final int SDLK_4 = 52; - public static final int SDLK_5 = 53; - public static final int SDLK_6 = 54; - public static final int SDLK_7 = 55; - public static final int SDLK_8 = 56; - public static final int SDLK_9 = 57; - public static final int SDLK_COLON = 58; - public static final int SDLK_SEMICOLON = 59; - public static final int SDLK_LESS = 60; - public static final int SDLK_EQUALS = 61; - public static final int SDLK_GREATER = 62; - public static final int SDLK_QUESTION = 63; - public static final int SDLK_AT = 64; - public static final int SDLK_LEFTBRACKET = 91; - public static final int SDLK_BACKSLASH = 92; - public static final int SDLK_RIGHTBRACKET = 93; - public static final int SDLK_CARET = 94; - public static final int SDLK_UNDERSCORE = 95; - public static final int SDLK_BACKQUOTE = 96; - public static final int SDLK_a = 97; - public static final int SDLK_b = 98; - public static final int SDLK_c = 99; - public static final int SDLK_d = 100; - public static final int SDLK_e = 101; - public static final int SDLK_f = 102; - public static final int SDLK_g = 103; - public static final int SDLK_h = 104; - public static final int SDLK_i = 105; - public static final int SDLK_j = 106; - public static final int SDLK_k = 107; - public static final int SDLK_l = 108; - public static final int SDLK_m = 109; - public static final int SDLK_n = 110; - public static final int SDLK_o = 111; - public static final int SDLK_p = 112; - public static final int SDLK_q = 113; - public static final int SDLK_r = 114; - public static final int SDLK_s = 115; - public static final int SDLK_t = 116; - public static final int SDLK_u = 117; - public static final int SDLK_v = 118; - public static final int SDLK_w = 119; - public static final int SDLK_x = 120; - public static final int SDLK_y = 121; - public static final int SDLK_z = 122; - public static final int SDLK_DELETE = 127; - public static final int SDLK_WORLD_0 = 160; - public static final int SDLK_WORLD_1 = 161; - public static final int SDLK_WORLD_2 = 162; - public static final int SDLK_WORLD_3 = 163; - public static final int SDLK_WORLD_4 = 164; - public static final int SDLK_WORLD_5 = 165; - public static final int SDLK_WORLD_6 = 166; - public static final int SDLK_WORLD_7 = 167; - public static final int SDLK_WORLD_8 = 168; - public static final int SDLK_WORLD_9 = 169; - public static final int SDLK_WORLD_10 = 170; - public static final int SDLK_WORLD_11 = 171; - public static final int SDLK_WORLD_12 = 172; - public static final int SDLK_WORLD_13 = 173; - public static final int SDLK_WORLD_14 = 174; - public static final int SDLK_WORLD_15 = 175; - public static final int SDLK_WORLD_16 = 176; - public static final int SDLK_WORLD_17 = 177; - public static final int SDLK_WORLD_18 = 178; - public static final int SDLK_WORLD_19 = 179; - public static final int SDLK_WORLD_20 = 180; - public static final int SDLK_WORLD_21 = 181; - public static final int SDLK_WORLD_22 = 182; - public static final int SDLK_WORLD_23 = 183; - public static final int SDLK_WORLD_24 = 184; - public static final int SDLK_WORLD_25 = 185; - public static final int SDLK_WORLD_26 = 186; - public static final int SDLK_WORLD_27 = 187; - public static final int SDLK_WORLD_28 = 188; - public static final int SDLK_WORLD_29 = 189; - public static final int SDLK_WORLD_30 = 190; - public static final int SDLK_WORLD_31 = 191; - public static final int SDLK_WORLD_32 = 192; - public static final int SDLK_WORLD_33 = 193; - public static final int SDLK_WORLD_34 = 194; - public static final int SDLK_WORLD_35 = 195; - public static final int SDLK_WORLD_36 = 196; - public static final int SDLK_WORLD_37 = 197; - public static final int SDLK_WORLD_38 = 198; - public static final int SDLK_WORLD_39 = 199; - public static final int SDLK_WORLD_40 = 200; - public static final int SDLK_WORLD_41 = 201; - public static final int SDLK_WORLD_42 = 202; - public static final int SDLK_WORLD_43 = 203; - public static final int SDLK_WORLD_44 = 204; - public static final int SDLK_WORLD_45 = 205; - public static final int SDLK_WORLD_46 = 206; - public static final int SDLK_WORLD_47 = 207; - public static final int SDLK_WORLD_48 = 208; - public static final int SDLK_WORLD_49 = 209; - public static final int SDLK_WORLD_50 = 210; - public static final int SDLK_WORLD_51 = 211; - public static final int SDLK_WORLD_52 = 212; - public static final int SDLK_WORLD_53 = 213; - public static final int SDLK_WORLD_54 = 214; - public static final int SDLK_WORLD_55 = 215; - public static final int SDLK_WORLD_56 = 216; - public static final int SDLK_WORLD_57 = 217; - public static final int SDLK_WORLD_58 = 218; - public static final int SDLK_WORLD_59 = 219; - public static final int SDLK_WORLD_60 = 220; - public static final int SDLK_WORLD_61 = 221; - public static final int SDLK_WORLD_62 = 222; - public static final int SDLK_WORLD_63 = 223; - public static final int SDLK_WORLD_64 = 224; - public static final int SDLK_WORLD_65 = 225; - public static final int SDLK_WORLD_66 = 226; - public static final int SDLK_WORLD_67 = 227; - public static final int SDLK_WORLD_68 = 228; - public static final int SDLK_WORLD_69 = 229; - public static final int SDLK_WORLD_70 = 230; - public static final int SDLK_WORLD_71 = 231; - public static final int SDLK_WORLD_72 = 232; - public static final int SDLK_WORLD_73 = 233; - public static final int SDLK_WORLD_74 = 234; - public static final int SDLK_WORLD_75 = 235; - public static final int SDLK_WORLD_76 = 236; - public static final int SDLK_WORLD_77 = 237; - public static final int SDLK_WORLD_78 = 238; - public static final int SDLK_WORLD_79 = 239; - public static final int SDLK_WORLD_80 = 240; - public static final int SDLK_WORLD_81 = 241; - public static final int SDLK_WORLD_82 = 242; - public static final int SDLK_WORLD_83 = 243; - public static final int SDLK_WORLD_84 = 244; - public static final int SDLK_WORLD_85 = 245; - public static final int SDLK_WORLD_86 = 246; - public static final int SDLK_WORLD_87 = 247; - public static final int SDLK_WORLD_88 = 248; - public static final int SDLK_WORLD_89 = 249; - public static final int SDLK_WORLD_90 = 250; - public static final int SDLK_WORLD_91 = 251; - public static final int SDLK_WORLD_92 = 252; - public static final int SDLK_WORLD_93 = 253; - public static final int SDLK_WORLD_94 = 254; - public static final int SDLK_WORLD_95 = 255; - public static final int SDLK_KP0 = 256; - public static final int SDLK_KP1 = 257; - public static final int SDLK_KP2 = 258; - public static final int SDLK_KP3 = 259; - public static final int SDLK_KP4 = 260; - public static final int SDLK_KP5 = 261; - public static final int SDLK_KP6 = 262; - public static final int SDLK_KP7 = 263; - public static final int SDLK_KP8 = 264; - public static final int SDLK_KP9 = 265; - public static final int SDLK_KP_PERIOD = 266; - public static final int SDLK_KP_DIVIDE = 267; - public static final int SDLK_KP_MULTIPLY = 268; - public static final int SDLK_KP_MINUS = 269; - public static final int SDLK_KP_PLUS = 270; - public static final int SDLK_KP_ENTER = 271; - public static final int SDLK_KP_EQUALS = 272; - public static final int SDLK_UP = 273; - public static final int SDLK_DOWN = 274; - public static final int SDLK_RIGHT = 275; - public static final int SDLK_LEFT = 276; - public static final int SDLK_INSERT = 277; - public static final int SDLK_HOME = 278; - public static final int SDLK_END = 279; - public static final int SDLK_PAGEUP = 280; - public static final int SDLK_PAGEDOWN = 281; - public static final int SDLK_F1 = 282; - public static final int SDLK_F2 = 283; - public static final int SDLK_F3 = 284; - public static final int SDLK_F4 = 285; - public static final int SDLK_F5 = 286; - public static final int SDLK_F6 = 287; - public static final int SDLK_F7 = 288; - public static final int SDLK_F8 = 289; - public static final int SDLK_F9 = 290; - public static final int SDLK_F10 = 291; - public static final int SDLK_F11 = 292; - public static final int SDLK_F12 = 293; - public static final int SDLK_F13 = 294; - public static final int SDLK_F14 = 295; - public static final int SDLK_F15 = 296; - public static final int SDLK_NUMLOCK = 300; - public static final int SDLK_CAPSLOCK = 301; - public static final int SDLK_SCROLLOCK = 302; - public static final int SDLK_RSHIFT = 303; - public static final int SDLK_LSHIFT = 304; - public static final int SDLK_RCTRL = 305; - public static final int SDLK_LCTRL = 306; - public static final int SDLK_RALT = 307; - public static final int SDLK_LALT = 308; - public static final int SDLK_RMETA = 309; - public static final int SDLK_LMETA = 310; - public static final int SDLK_LSUPER = 311; - public static final int SDLK_RSUPER = 312; - public static final int SDLK_MODE = 313; - public static final int SDLK_COMPOSE = 314; - public static final int SDLK_HELP = 315; - public static final int SDLK_PRINT = 316; - public static final int SDLK_SYSREQ = 317; - public static final int SDLK_BREAK = 318; - public static final int SDLK_MENU = 319; - public static final int SDLK_POWER = 320; - public static final int SDLK_EURO = 321; - public static final int SDLK_UNDO = 322; - - public static final int SDLK_NO_REMAP = 512; -} - -// Autogenerated by hand with a command: -// grep 'SDL_SCANCODE_' SDL_scancode.h | sed 's/SDL_SCANCODE_\([a-zA-Z0-9_]\+\).*[=] \([0-9]\+\).*/public static final int SDLK_\1 = \2;/' >> Keycodes.java -class SDL_1_3_Keycodes -{ - public static final int SDLK_UNKNOWN = 0; - public static final int SDLK_A = 4; - public static final int SDLK_B = 5; - public static final int SDLK_C = 6; - public static final int SDLK_D = 7; - public static final int SDLK_E = 8; - public static final int SDLK_F = 9; - public static final int SDLK_G = 10; - public static final int SDLK_H = 11; - public static final int SDLK_I = 12; - public static final int SDLK_J = 13; - public static final int SDLK_K = 14; - public static final int SDLK_L = 15; - public static final int SDLK_M = 16; - public static final int SDLK_N = 17; - public static final int SDLK_O = 18; - public static final int SDLK_P = 19; - public static final int SDLK_Q = 20; - public static final int SDLK_R = 21; - public static final int SDLK_S = 22; - public static final int SDLK_T = 23; - public static final int SDLK_U = 24; - public static final int SDLK_V = 25; - public static final int SDLK_W = 26; - public static final int SDLK_X = 27; - public static final int SDLK_Y = 28; - public static final int SDLK_Z = 29; - public static final int SDLK_1 = 30; - public static final int SDLK_2 = 31; - public static final int SDLK_3 = 32; - public static final int SDLK_4 = 33; - public static final int SDLK_5 = 34; - public static final int SDLK_6 = 35; - public static final int SDLK_7 = 36; - public static final int SDLK_8 = 37; - public static final int SDLK_9 = 38; - public static final int SDLK_0 = 39; - public static final int SDLK_RETURN = 40; - public static final int SDLK_ESCAPE = 41; - public static final int SDLK_BACKSPACE = 42; - public static final int SDLK_TAB = 43; - public static final int SDLK_SPACE = 44; - public static final int SDLK_MINUS = 45; - public static final int SDLK_EQUALS = 46; - public static final int SDLK_LEFTBRACKET = 47; - public static final int SDLK_RIGHTBRACKET = 48; - public static final int SDLK_BACKSLASH = 49; - public static final int SDLK_NONUSHASH = 50; - public static final int SDLK_SEMICOLON = 51; - public static final int SDLK_APOSTROPHE = 52; - public static final int SDLK_GRAVE = 53; - public static final int SDLK_COMMA = 54; - public static final int SDLK_PERIOD = 55; - public static final int SDLK_SLASH = 56; - public static final int SDLK_CAPSLOCK = 57; - public static final int SDLK_F1 = 58; - public static final int SDLK_F2 = 59; - public static final int SDLK_F3 = 60; - public static final int SDLK_F4 = 61; - public static final int SDLK_F5 = 62; - public static final int SDLK_F6 = 63; - public static final int SDLK_F7 = 64; - public static final int SDLK_F8 = 65; - public static final int SDLK_F9 = 66; - public static final int SDLK_F10 = 67; - public static final int SDLK_F11 = 68; - public static final int SDLK_F12 = 69; - public static final int SDLK_PRINTSCREEN = 70; - public static final int SDLK_SCROLLLOCK = 71; - public static final int SDLK_PAUSE = 72; - public static final int SDLK_INSERT = 73; - public static final int SDLK_HOME = 74; - public static final int SDLK_PAGEUP = 75; - public static final int SDLK_DELETE = 76; - public static final int SDLK_END = 77; - public static final int SDLK_PAGEDOWN = 78; - public static final int SDLK_RIGHT = 79; - public static final int SDLK_LEFT = 80; - public static final int SDLK_DOWN = 81; - public static final int SDLK_UP = 82; - public static final int SDLK_NUMLOCKCLEAR = 83; - public static final int SDLK_KP_DIVIDE = 84; - public static final int SDLK_KP_MULTIPLY = 85; - public static final int SDLK_KP_MINUS = 86; - public static final int SDLK_KP_PLUS = 87; - public static final int SDLK_KP_ENTER = 88; - public static final int SDLK_KP_1 = 89; - public static final int SDLK_KP_2 = 90; - public static final int SDLK_KP_3 = 91; - public static final int SDLK_KP_4 = 92; - public static final int SDLK_KP_5 = 93; - public static final int SDLK_KP_6 = 94; - public static final int SDLK_KP_7 = 95; - public static final int SDLK_KP_8 = 96; - public static final int SDLK_KP_9 = 97; - public static final int SDLK_KP_0 = 98; - public static final int SDLK_KP_PERIOD = 99; - public static final int SDLK_NONUSBACKSLASH = 100; - public static final int SDLK_APPLICATION = 101; - public static final int SDLK_POWER = 102; - public static final int SDLK_KP_EQUALS = 103; - public static final int SDLK_F13 = 104; - public static final int SDLK_F14 = 105; - public static final int SDLK_F15 = 106; - public static final int SDLK_F16 = 107; - public static final int SDLK_F17 = 108; - public static final int SDLK_F18 = 109; - public static final int SDLK_F19 = 110; - public static final int SDLK_F20 = 111; - public static final int SDLK_F21 = 112; - public static final int SDLK_F22 = 113; - public static final int SDLK_F23 = 114; - public static final int SDLK_F24 = 115; - public static final int SDLK_EXECUTE = 116; - public static final int SDLK_HELP = 117; - public static final int SDLK_MENU = 118; - public static final int SDLK_SELECT = 119; - public static final int SDLK_STOP = 120; - public static final int SDLK_AGAIN = 121; - public static final int SDLK_UNDO = 122; - public static final int SDLK_CUT = 123; - public static final int SDLK_COPY = 124; - public static final int SDLK_PASTE = 125; - public static final int SDLK_FIND = 126; - public static final int SDLK_MUTE = 127; - public static final int SDLK_VOLUMEUP = 128; - public static final int SDLK_VOLUMEDOWN = 129; - public static final int SDLK_KP_COMMA = 133; - public static final int SDLK_KP_EQUALSAS400 = 134; - public static final int SDLK_INTERNATIONAL1 = 135; - public static final int SDLK_INTERNATIONAL2 = 136; - public static final int SDLK_INTERNATIONAL3 = 137; - public static final int SDLK_INTERNATIONAL4 = 138; - public static final int SDLK_INTERNATIONAL5 = 139; - public static final int SDLK_INTERNATIONAL6 = 140; - public static final int SDLK_INTERNATIONAL7 = 141; - public static final int SDLK_INTERNATIONAL8 = 142; - public static final int SDLK_INTERNATIONAL9 = 143; - public static final int SDLK_LANG1 = 144; - public static final int SDLK_LANG2 = 145; - public static final int SDLK_LANG3 = 146; - public static final int SDLK_LANG4 = 147; - public static final int SDLK_LANG5 = 148; - public static final int SDLK_LANG6 = 149; - public static final int SDLK_LANG7 = 150; - public static final int SDLK_LANG8 = 151; - public static final int SDLK_LANG9 = 152; - public static final int SDLK_ALTERASE = 153; - public static final int SDLK_SYSREQ = 154; - public static final int SDLK_CANCEL = 155; - public static final int SDLK_CLEAR = 156; - public static final int SDLK_PRIOR = 157; - public static final int SDLK_RETURN2 = 158; - public static final int SDLK_SEPARATOR = 159; - public static final int SDLK_OUT = 160; - public static final int SDLK_OPER = 161; - public static final int SDLK_CLEARAGAIN = 162; - public static final int SDLK_CRSEL = 163; - public static final int SDLK_EXSEL = 164; - public static final int SDLK_KP_00 = 176; - public static final int SDLK_KP_000 = 177; - public static final int SDLK_THOUSANDSSEPARATOR = 178; - public static final int SDLK_DECIMALSEPARATOR = 179; - public static final int SDLK_CURRENCYUNIT = 180; - public static final int SDLK_CURRENCYSUBUNIT = 181; - public static final int SDLK_KP_LEFTPAREN = 182; - public static final int SDLK_KP_RIGHTPAREN = 183; - public static final int SDLK_KP_LEFTBRACE = 184; - public static final int SDLK_KP_RIGHTBRACE = 185; - public static final int SDLK_KP_TAB = 186; - public static final int SDLK_KP_BACKSPACE = 187; - public static final int SDLK_KP_A = 188; - public static final int SDLK_KP_B = 189; - public static final int SDLK_KP_C = 190; - public static final int SDLK_KP_D = 191; - public static final int SDLK_KP_E = 192; - public static final int SDLK_KP_F = 193; - public static final int SDLK_KP_XOR = 194; - public static final int SDLK_KP_POWER = 195; - public static final int SDLK_KP_PERCENT = 196; - public static final int SDLK_KP_LESS = 197; - public static final int SDLK_KP_GREATER = 198; - public static final int SDLK_KP_AMPERSAND = 199; - public static final int SDLK_KP_DBLAMPERSAND = 200; - public static final int SDLK_KP_VERTICALBAR = 201; - public static final int SDLK_KP_DBLVERTICALBAR = 202; - public static final int SDLK_KP_COLON = 203; - public static final int SDLK_KP_HASH = 204; - public static final int SDLK_KP_SPACE = 205; - public static final int SDLK_KP_AT = 206; - public static final int SDLK_KP_EXCLAM = 207; - public static final int SDLK_KP_MEMSTORE = 208; - public static final int SDLK_KP_MEMRECALL = 209; - public static final int SDLK_KP_MEMCLEAR = 210; - public static final int SDLK_KP_MEMADD = 211; - public static final int SDLK_KP_MEMSUBTRACT = 212; - public static final int SDLK_KP_MEMMULTIPLY = 213; - public static final int SDLK_KP_MEMDIVIDE = 214; - public static final int SDLK_KP_PLUSMINUS = 215; - public static final int SDLK_KP_CLEAR = 216; - public static final int SDLK_KP_CLEARENTRY = 217; - public static final int SDLK_KP_BINARY = 218; - public static final int SDLK_KP_OCTAL = 219; - public static final int SDLK_KP_DECIMAL = 220; - public static final int SDLK_KP_HEXADECIMAL = 221; - public static final int SDLK_LCTRL = 224; - public static final int SDLK_LSHIFT = 225; - public static final int SDLK_LALT = 226; - public static final int SDLK_LGUI = 227; - public static final int SDLK_RCTRL = 228; - public static final int SDLK_RSHIFT = 229; - public static final int SDLK_RALT = 230; - public static final int SDLK_RGUI = 231; - public static final int SDLK_MODE = 257; - public static final int SDLK_AUDIONEXT = 258; - public static final int SDLK_AUDIOPREV = 259; - public static final int SDLK_AUDIOSTOP = 260; - public static final int SDLK_AUDIOPLAY = 261; - public static final int SDLK_AUDIOMUTE = 262; - public static final int SDLK_MEDIASELECT = 263; - public static final int SDLK_WWW = 264; - public static final int SDLK_MAIL = 265; - public static final int SDLK_CALCULATOR = 266; - public static final int SDLK_COMPUTER = 267; - public static final int SDLK_AC_SEARCH = 268; - public static final int SDLK_AC_HOME = 269; - public static final int SDLK_AC_BACK = 270; - public static final int SDLK_AC_FORWARD = 271; - public static final int SDLK_AC_STOP = 272; - public static final int SDLK_AC_REFRESH = 273; - public static final int SDLK_AC_BOOKMARKS = 274; - public static final int SDLK_BRIGHTNESSDOWN = 275; - public static final int SDLK_BRIGHTNESSUP = 276; - public static final int SDLK_DISPLAYSWITCH = 277; - public static final int SDLK_KBDILLUMTOGGLE = 278; - public static final int SDLK_KBDILLUMDOWN = 279; - public static final int SDLK_KBDILLUMUP = 280; - public static final int SDLK_EJECT = 281; - public static final int SDLK_SLEEP = 282; - - public static final int SDLK_NO_REMAP = 512; -} - -class SDL_Keys -{ - public static String [] names = null; - public static Integer [] values = null; - - public static String [] namesSorted = null; - public static Integer [] namesSortedIdx = null; - public static Integer [] namesSortedBackIdx = null; - - static final int JAVA_KEYCODE_LAST = 255; // Android 2.3 added several new gaming keys, Android 3.1 added even more - keep in sync with javakeycodes.h - - static String getName(int v) - { - for( int f = 0; f < values.length; f++ ) - { - if( values[f] == v ) - return names[f]; - } - return names[0]; - } - - static - { - ArrayList Names = new ArrayList (); - ArrayList Values = new ArrayList (); - Field [] fields = SDL_1_2_Keycodes.class.getDeclaredFields(); - if( Globals.Using_SDL_1_3 ) - { - fields = SDL_1_3_Keycodes.class.getDeclaredFields(); - } - - try { - for(Field f: fields) - { - Values.add(f.getInt(null)); - Names.add(f.getName().substring(5).toUpperCase()); - } - } catch(IllegalAccessException e) {}; - - // Sort by value - for( int i = 0; i < Values.size(); i++ ) - { - for( int j = i; j < Values.size(); j++ ) - { - if( Values.get(i) > Values.get(j) ) - { - int x = Values.get(i); - Values.set(i, Values.get(j)); - Values.set(j, x); - String s = Names.get(i); - Names.set(i, Names.get(j)); - Names.set(j, s); - } - } - } - - names = Names.toArray(new String[0]); - values = Values.toArray(new Integer[0]); - namesSorted = Names.toArray(new String[0]); - namesSortedIdx = new Integer[values.length]; - namesSortedBackIdx = new Integer[values.length]; - Arrays.sort(namesSorted); - for( int i = 0; i < namesSorted.length; i++ ) - { - for( int j = 0; j < namesSorted.length; j++ ) - { - if( namesSorted[i].equals( names[j] ) ) - { - namesSortedIdx[i] = j; - namesSortedBackIdx[j] = i; - break; - } - } - } - } -} diff --git a/project/javaSDL2/MainActivity.java b/project/javaSDL2/MainActivity.java deleted file mode 100644 index 463fc1d6b..000000000 --- a/project/javaSDL2/MainActivity.java +++ /dev/null @@ -1,1241 +0,0 @@ -/* -Simple DirectMedia Layer -Java source code (C) 2009-2012 Sergii Pylypenko - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. -*/ - -package net.sourceforge.clonekeenplus; - -import org.libsdl.app.SDLActivity; -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.view.MotionEvent; -import android.view.KeyEvent; -import android.view.Window; -import android.view.WindowManager; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import android.widget.EditText; -import android.text.Editable; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.FrameLayout; -import android.graphics.drawable.Drawable; -import android.graphics.Color; -import android.content.res.Configuration; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Intent; -import android.view.View.OnKeyListener; -import android.view.MenuItem; -import android.view.Menu; -import android.view.Gravity; -import android.text.method.TextKeyListener; -import java.util.LinkedList; -import java.io.SequenceInputStream; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.FileOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.util.zip.*; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.Set; -import android.text.SpannedString; -import java.io.BufferedReader; -import java.io.BufferedInputStream; -import java.io.InputStreamReader; -import android.view.inputmethod.InputMethodManager; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import java.util.concurrent.Semaphore; -import android.content.pm.ActivityInfo; -import android.view.Display; -import android.text.InputType; -import android.util.Log; - -/* - * A sample wrapper class that just calls SDLActivity - */ - -public class MainActivity extends SDLActivity -{ - - // Load the .so - static { - System.loadLibrary("sdl_native_helpers"); - System.loadLibrary("sdl2_image"); - System.loadLibrary("application"); - System.loadLibrary("sdl_main"); - } - - @Override - protected void onCreate(Bundle savedInstanceState) - { - Settings.Load(this); - Settings.Apply(this); - - setRequestedOrientation(Globals.HorizontalOrientation ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - - instance = this; - - // fullscreen mode - requestWindowFeature(Window.FEATURE_NO_TITLE); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - - if(Globals.InhibitSuspend) - getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, - WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - - super.onCreate(savedInstanceState); - - Log.i("SDL", "libSDL: Creating startup screen"); - _layout = new LinearLayout(this); _layout.setOrientation(LinearLayout.VERTICAL); - _layout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - _layout2 = new LinearLayout(this); - _layout2.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - final Semaphore loadedLibraries = new Semaphore(0); - - if( Globals.StartupMenuButtonTimeout > 0 ) - { - _btn = new Button(this); - _btn.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - _btn.setText(getResources().getString(R.string.device_change_cfg)); - class onClickListener implements View.OnClickListener - { - public MainActivity p; - onClickListener( MainActivity _p ) { p = _p; } - public void onClick(View v) - { - setUpStatusLabel(); - Log.i("SDL", "libSDL: User clicked change phone config button"); - loadedLibraries.acquireUninterruptibly(); - SettingsMenu.showConfig(p, false); - } - }; - _btn.setOnClickListener(new onClickListener(this)); - - _layout2.addView(_btn); - } - - _layout.addView(_layout2); - - ImageView img = new ImageView(this); - - img.setScaleType(ImageView.ScaleType.FIT_CENTER ); // FIT_XY - try - { - img.setImageDrawable(Drawable.createFromStream(getAssets().open("logo.png"), "logo.png")); - } - catch(Exception e) - { - img.setImageResource(R.drawable.publisherlogo); - } - img.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - _layout.addView(img); - - //_videoLayout = new FrameLayout(this); - //_videoLayout.addView(_layout); - mLayout.addView(_layout); - - setContentView(mLayout); - - _ad = new Advertisement(this); - if( _ad.getView() != null ) - { - mLayout.addView(_ad.getView()); - _ad.getView().setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM | Gravity.RIGHT)); - } - -// setContentView(_videoLayout); - - class Callback implements Runnable - { - MainActivity p; - Callback( MainActivity _p ) { p = _p; } - public void run() - { - try { - Thread.sleep(200); - } catch( InterruptedException e ) {}; - - if(p.mAudioThread == null) - { - Log.i("SDL", "libSDL: Loading libraries"); - p.LoadLibraries(); - p.mAudioThread = new AudioThread(p); - Log.i("SDL", "libSDL: Loading settings"); - final Semaphore loaded = new Semaphore(0); - class Callback2 implements Runnable - { - public MainActivity Parent; - public void run() - { - Settings.Load(Parent); - loaded.release(); - loadedLibraries.release(); - } - } - Callback2 cb = new Callback2(); - cb.Parent = p; - p.runOnUiThread(cb); - loaded.acquireUninterruptibly(); - if(!Globals.CompatibilityHacksStaticInit) - p.LoadApplicationLibrary(p); - } - - if( !Settings.settingsChanged ) - { - if( Globals.StartupMenuButtonTimeout > 0 ) - { - Log.i("SDL", "libSDL: " + String.valueOf(Globals.StartupMenuButtonTimeout) + "-msec timeout in startup screen"); - try { - Thread.sleep(Globals.StartupMenuButtonTimeout); - } catch( InterruptedException e ) {}; - } - if( Settings.settingsChanged ) - return; - Log.i("SDL", "libSDL: Timeout reached in startup screen, process with downloader"); - p.startDownloader(); - } - - - } - }; - (new Thread(new Callback(this))).start(); - } - public void setUpStatusLabel() - { - MainActivity Parent = this; // Too lazy to rename - if( Parent._btn != null ) - { - Parent._layout2.removeView(Parent._btn); - Parent._btn = null; - } - if( Parent._tv == null ) - { - Parent._tv = new TextView(Parent); - Parent._tv.setMaxLines(2); // To show some long texts on smaller devices - Parent._tv.setMinLines(2); // Otherwise the background picture is getting resized at random, which does not look good - Parent._tv.setText(R.string.init); - Parent._layout2.addView(Parent._tv); - } - } - - public void startDownloader() - { - Log.i("SDL", "libSDL: Starting data downloader"); - class Callback implements Runnable - { - public MainActivity Parent; - public void run() - { - setUpStatusLabel(); - Log.i("SDL", "libSDL: Starting downloader"); - if( Parent.downloader == null ) - Parent.downloader = new DataDownloader(Parent, Parent._tv); - } - } - Callback cb = new Callback(); - cb.Parent = this; - this.runOnUiThread(cb); - } - - public void initSDL() - { - (new Thread(new Runnable() - { - public void run() - { - //int tries = 30; - while( isCurrentOrientationHorizontal() != Globals.HorizontalOrientation ) - { - Log.i("SDL", "libSDL: Waiting for screen orientation to change - the device is probably in the lockscreen mode"); - try { - Thread.sleep(500); - } catch( Exception e ) {} - /* - tries--; - if( tries <= 0 ) - { - Log.i("SDL", "libSDL: Giving up waiting for screen orientation change"); - break; - } - */ - if( _isPaused ) - { - Log.i("SDL", "libSDL: Application paused, cancelling SDL initialization until it will be brought to foreground"); - return; - } - } - runOnUiThread(new Runnable() - { - public void run() - { - initSDLInternal(); - } - }); - } - })).start(); - } - - private void initSDLInternal() - { - if(sdlInited) - return; - Log.i("SDL", "libSDL: Initializing video and SDL application"); - - sdlInited = true; - - mLayout.removeView(_layout); - - if( _ad.getView() != null ) - mLayout.removeView(_ad.getView()); - _layout = null; - _layout2 = null; - _btn = null; - _tv = null; - //_inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - /*_videoLayout = new FrameLayout(this); - SetLayerType.get().setLayerType(_videoLayout); - setContentView(_videoLayout);*/ - - - /*mGLView = new DemoGLSurfaceView(this); - SetLayerType.get().setLayerType(mGLView); - mLayout.addView(mGLView);*/ - //mGLView.setFocusableInTouchMode(true); - //mGLView.setFocusable(true); - //mGLView.requestFocus(); - /*if( _ad.getView() != null ) - { - _videoLayout.addView(_ad.getView()); - _ad.getView().setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.TOP | Gravity.RIGHT)); - }*/ - // Receive keyboard events - /*DimSystemStatusBar.get().dim(_videoLayout);*/ - //DimSystemStatusBar.get().dim(mGLView); - } - - @Override - protected void onPause() { - /*if( downloader != null ) - { - synchronized( downloader ) - { - downloader.setStatusField(null); - } - } - _isPaused = true; - if( mGLView != null ) - mGLView.onPause();*/ - //if( _ad.getView() != null ) - // _ad.getView().onPause(); - super.onPause(); - } - - @Override - protected void onResume() { - Settings.Apply(this); - super.onResume(); - /*if( mGLView != null ) - { - mGLView.onResume(); - DimSystemStatusBar.get().dim(_videoLayout); - DimSystemStatusBar.get().dim(mGLView); - } - if( downloader != null ) - { - synchronized( downloader ) - { - downloader.setStatusField(_tv); - if( downloader.DownloadComplete ) - { - initSDL(); - } - } - } - //if( _ad.getView() != null ) - // _ad.getView().onResume(); - _isPaused = false;*/ - } - - @Override - public void onWindowFocusChanged (boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - Log.i("SDL", "libSDL: onWindowFocusChanged: " + hasFocus + " - sending onPause/onResume"); - /*if (hasFocus == false) - onPause(); - else - onResume();*/ - /* - if (hasFocus == false) { - synchronized(textInput) { - // Send 'SDLK_PAUSE' (to enter pause mode) to native code: - DemoRenderer.nativeTextInput( 19, 19 ); - } - } - */ - } - - public boolean isPaused() - { - return _isPaused; - } - - @Override - protected void onDestroy() - { - /*if( downloader != null ) - { - synchronized( downloader ) - { - downloader.setStatusField(null); - } - } - if( mGLView != null ) - mGLView.exitApp();*/ - super.onDestroy(); -/* try{ - Thread.sleep(2000); // The event is sent asynchronously, allow app to save it's state, and call exit() itself. - } catch (InterruptedException e) {} - System.exit(0);*/ - } - - public void showScreenKeyboardWithoutTextInputField() - { - //_inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); - //_inputManager.showSoftInput(mGLView, InputMethodManager.SHOW_FORCED); - //getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } - - public void showScreenKeyboard(final String oldText, boolean sendBackspace) - { - if(Globals.CompatibilityHacksTextInputEmulatesHwKeyboard) - { - showScreenKeyboardWithoutTextInputField(); - return; - } - if(_screenKeyboard != null) - return; - class simpleKeyListener implements OnKeyListener - { - MainActivity _parent; - boolean sendBackspace; - simpleKeyListener(MainActivity parent, boolean sendBackspace) { _parent = parent; this.sendBackspace = sendBackspace; }; - public boolean onKey(View v, int keyCode, KeyEvent event) - { - if ((event.getAction() == KeyEvent.ACTION_UP) && ( - keyCode == KeyEvent.KEYCODE_ENTER || - keyCode == KeyEvent.KEYCODE_BACK || - keyCode == KeyEvent.KEYCODE_MENU || - keyCode == KeyEvent.KEYCODE_BUTTON_A || - keyCode == KeyEvent.KEYCODE_BUTTON_B || - keyCode == KeyEvent.KEYCODE_BUTTON_X || - keyCode == KeyEvent.KEYCODE_BUTTON_Y || - keyCode == KeyEvent.KEYCODE_BUTTON_1 || - keyCode == KeyEvent.KEYCODE_BUTTON_2 || - keyCode == KeyEvent.KEYCODE_BUTTON_3 || - keyCode == KeyEvent.KEYCODE_BUTTON_4 )) - { - //_parent.hideScreenKeyboard(); - return true; - } - if (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_CLEAR) - { - if (sendBackspace && event.getAction() == KeyEvent.ACTION_UP) - { - synchronized(textInput) { - //DemoRenderer.nativeTextInput( 8, 0 ); // Send backspace to native code - } - } - // EditText deletes two characters at a time, here's a hacky fix - if (event.getAction() == KeyEvent.ACTION_DOWN && (event.getFlags() | KeyEvent.FLAG_SOFT_KEYBOARD) != 0) - { - EditText t = (EditText) v; - int start = t.getSelectionStart(); //get cursor starting position - int end = t.getSelectionEnd(); //get cursor ending position - if ( start < 0 ) - return true; - if ( end < 0 || end == start ) - { - start --; - if ( start < 0 ) - return true; - end = start + 1; - } - t.setText(t.getText().toString().substring(0, start) + t.getText().toString().substring(end)); - t.setSelection(start); - return true; - } - } - //Log.i("SDL", "Key " + keyCode + " flags " + event.getFlags() + " action " + event.getAction()); - return false; - } - }; - _screenKeyboard = new EditText(this); - // This code does not work - /* - _screenKeyboard.setMaxLines(100); - ViewGroup.LayoutParams layout = _screenKeyboard.getLayoutParams(); - if( layout != null ) - { - layout.width = ViewGroup.LayoutParams.FILL_PARENT; - layout.height = ViewGroup.LayoutParams.FILL_PARENT; - _screenKeyboard.setLayoutParams(layout); - } - _screenKeyboard.setGravity(android.view.Gravity.BOTTOM | android.view.Gravity.LEFT); - */ - String hint = _screenKeyboardHintMessage; - _screenKeyboard.setHint(hint != null ? hint : getString(R.string.text_edit_click_here)); - _screenKeyboard.setText(oldText); - _screenKeyboard.setSelection(_screenKeyboard.getText().length()); - _screenKeyboard.setOnKeyListener(new simpleKeyListener(this, sendBackspace)); - _screenKeyboard.setBackgroundColor(Color.BLACK); // Full opaque - do not show semi-transparent edit box, it's confusing - _screenKeyboard.setTextColor(Color.WHITE); // Just to be sure about gamma - if( isRunningOnOUYA() ) - _screenKeyboard.setPadding(100, 100, 100, 100); // Bad bad HDMI TVs all have cropped borders - //_videoLayout.addView(_screenKeyboard); - //_screenKeyboard.setKeyListener(new TextKeyListener(TextKeyListener.Capitalize.NONE, false)); - _screenKeyboard.setInputType(InputType.TYPE_CLASS_TEXT); - _screenKeyboard.setFocusableInTouchMode(true); - _screenKeyboard.setFocusable(true); - _screenKeyboard.requestFocus(); - _inputManager.showSoftInput(_screenKeyboard, InputMethodManager.SHOW_IMPLICIT); - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - // Hack to try to force on-screen keyboard - final EditText keyboard = _screenKeyboard; - keyboard.postDelayed( new Runnable() - { - public void run() - { - keyboard.requestFocus(); - //_inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); - _inputManager.showSoftInput(keyboard, InputMethodManager.SHOW_FORCED); - // Hack from Stackoverflow, to force text input on Ouya - keyboard.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN , 0, 0, 0)); - keyboard.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP , 0, 0, 0)); - keyboard.postDelayed( new Runnable() - { - public void run() - { - keyboard.setSelection(keyboard.getText().length()); - } - }, 100 ); - } - }, 500 ); - }; - - /*public void hideScreenKeyboard() - { - if(_screenKeyboard == null) - return; - - synchronized(textInput) - { - String text = _screenKeyboard.getText().toString();*/ - /*for(int i = 0; i < text.length(); i++) - { - DemoRenderer.nativeTextInput( (int)text.charAt(i), (int)text.codePointAt(i) ); - }*/ - /*} - //DemoRenderer.nativeTextInputFinished(); - _inputManager.hideSoftInputFromWindow(_screenKeyboard.getWindowToken(), 0); - mLayout.removeView(_screenKeyboard); - _screenKeyboard = null; - mGLView.setFocusableInTouchMode(true); - mGLView.setFocusable(true); - mGLView.requestFocus(); - };*/ - - /*public boolean isScreenKeyboardShown() - { - return _screenKeyboard != null; - }; - - public void setScreenKeyboardHintMessage(String s) - { - _screenKeyboardHintMessage = s; - //Log.i("SDL", "setScreenKeyboardHintMessage: " + (_screenKeyboardHintMessage != null ? _screenKeyboardHintMessage : getString(R.string.text_edit_click_here))); - runOnUiThread(new Runnable() - { - public void run() - { - if( _screenKeyboard != null ) - { - String hint = _screenKeyboardHintMessage; - _screenKeyboard.setHint(hint != null ? hint : getString(R.string.text_edit_click_here)); - } - } - } ); - } - - final static int ADVERTISEMENT_POSITION_RIGHT = -1; - final static int ADVERTISEMENT_POSITION_BOTTOM = -1; - final static int ADVERTISEMENT_POSITION_CENTER = -2; - - public void setAdvertisementPosition(int x, int y) - { - - if( _ad.getView() != null ) - { - final FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - layout.gravity = 0; - layout.leftMargin = 0; - layout.topMargin = 0; - if( x == ADVERTISEMENT_POSITION_RIGHT ) - layout.gravity |= Gravity.RIGHT; - else if ( x == ADVERTISEMENT_POSITION_CENTER ) - layout.gravity |= Gravity.CENTER_HORIZONTAL; - else - { - layout.gravity |= Gravity.LEFT; - layout.leftMargin = x; - } - if( y == ADVERTISEMENT_POSITION_BOTTOM ) - layout.gravity |= Gravity.BOTTOM; - else if ( x == ADVERTISEMENT_POSITION_CENTER ) - layout.gravity |= Gravity.CENTER_VERTICAL; - else - { - layout.gravity |= Gravity.TOP; - layout.topMargin = y; - } - class Callback implements Runnable - { - public void run() - { - _ad.getView().setLayoutParams(layout); - } - }; - runOnUiThread(new Callback()); - } - } - public void setAdvertisementVisible(final int visible) - { - if( _ad.getView() != null ) - { - class Callback implements Runnable - { - public void run() - { - if( visible == 0 ) - _ad.getView().setVisibility(View.GONE); - else - _ad.getView().setVisibility(View.VISIBLE); - } - } - runOnUiThread(new Callback()); - } - } - - public void getAdvertisementParams(int params[]) - { - for( int i = 0; i < 5; i++ ) - params[i] = 0; - if( _ad.getView() != null ) - { - params[0] = (_ad.getView().getVisibility() == View.VISIBLE) ? 1 : 0; - FrameLayout.LayoutParams layout = (FrameLayout.LayoutParams) _ad.getView().getLayoutParams(); - params[1] = layout.leftMargin; - params[2] = layout.topMargin; - params[3] = _ad.getView().getMeasuredWidth(); - params[4] = _ad.getView().getMeasuredHeight(); - } - }*/ - /*public void requestNewAdvertisement() - { - if( _ad.getView() != null ) - { - class Callback implements Runnable - { - public void run() - { - _ad.requestNewAd(); - } - } - runOnUiThread(new Callback()); - } - }*/ - - /*@Override - public boolean onKeyDown(int keyCode, final KeyEvent event) - { - if(_screenKeyboard != null) - _screenKeyboard.onKeyDown(keyCode, event); - else - if( mGLView != null ) - { - if( mGLView.nativeKey( keyCode, 1 ) == 0 ) - return super.onKeyDown(keyCode, event); - }*/ - /* - else - if( keyCode == KeyEvent.KEYCODE_BACK && downloader != null ) - { - if( downloader.DownloadFailed ) - System.exit(1); - if( !downloader.DownloadComplete ) - onStop(); - } - */ - /*else - if( keyListener != null ) - { - keyListener.onKeyEvent(keyCode); - } - return true;*/ - //} - - /*@Override - public boolean onKeyUp(int keyCode, final KeyEvent event) - { - if(_screenKeyboard != null) - _screenKeyboard.onKeyUp(keyCode, event); - else - if( mGLView != null ) - { - if( mGLView.nativeKey( keyCode, 0 ) == 0 ) - return super.onKeyUp(keyCode, event); - if( keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU ) - { - //DimSystemStatusBar.get().dim(_videoLayout); - DimSystemStatusBar.get().dim(mGLView); - } - } - return true; - }*/ - - /*@Override - public boolean dispatchTouchEvent(final MotionEvent ev) - {*/ - //Log.i("SDL", "dispatchTouchEvent: " + ev.getAction() + " coords " + ev.getX() + ":" + ev.getY() ); - /*if(_screenKeyboard != null) - _screenKeyboard.dispatchTouchEvent(ev); - else - if( _ad.getView() != null && // User clicked the advertisement, ignore when user moved finger from game screen to advertisement or touches screen with several fingers - ((ev.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN || - (ev.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) && - _ad.getView().getLeft() <= (int)ev.getX() && - _ad.getView().getRight() > (int)ev.getX() && - _ad.getView().getTop() <= (int)ev.getY() && - _ad.getView().getBottom() > (int)ev.getY() ) - return super.dispatchTouchEvent(ev); - else - if(mGLView != null) - mGLView.onTouchEvent(ev); - else - if( _btn != null ) - return _btn.dispatchTouchEvent(ev); - else - /*if( touchListener != null ) - touchListener.onTouchEvent(ev);*/ -/* - return true; - }*/ - -/* @Override - public boolean dispatchGenericMotionEvent (MotionEvent ev) - {*/ - //Log.i("SDL", "dispatchGenericMotionEvent: " + ev.getAction() + " coords " + ev.getX() + ":" + ev.getY() ); - // This code fails to run for Android 1.6, so there will be no generic motion event for Andorid screen keyboard - /* - if(_screenKeyboard != null) - _screenKeyboard.dispatchGenericMotionEvent(ev); - else - */ - /*if(mGLView != null) - mGLView.onGenericMotionEvent(ev); - return true;*/ -// } - - @Override - public void onConfigurationChanged(Configuration newConfig) - { - super.onConfigurationChanged(newConfig); - // Do nothing here - } - - public void setText(final String t) - { - class Callback implements Runnable - { - MainActivity Parent; - public SpannedString text; - public void run() - { - Parent.setUpStatusLabel(); - if(Parent._tv != null) - Parent._tv.setText(text); - } - } - Callback cb = new Callback(); - cb.text = new SpannedString(t); - cb.Parent = this; - this.runOnUiThread(cb); - } - - public void showTaskbarNotification() - { - showTaskbarNotification("SDL application paused", "SDL application", "Application is paused, click to activate"); - } - - // Stolen from SDL port by Mamaich - public void showTaskbarNotification(String text0, String text1, String text2) - { - NotificationManager NotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - Intent intent = new Intent(this, MainActivity.class); - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, Intent.FLAG_ACTIVITY_NEW_TASK); - Notification n = new Notification(R.drawable.icon, text0, System.currentTimeMillis()); - n.setLatestEventInfo(this, text1, text2, pendingIntent); - NotificationManager.notify(NOTIFY_ID, n); - } - - public void hideTaskbarNotification() - { - NotificationManager NotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - NotificationManager.cancel(NOTIFY_ID); - } - - public void LoadLibraries() - { - try - { - if(Globals.NeedGles2) - System.loadLibrary("GLESv2"); - Log.i("SDL", "libSDL: loaded GLESv2 lib"); - } - catch ( UnsatisfiedLinkError e ) - { - Log.i("SDL", "libSDL: Cannot load GLESv2 lib"); - } - - // Load all libraries - try - { - for(String l : Globals.AppLibraries) - { - try - { - String libname = System.mapLibraryName(l); - File libpath = new File(getFilesDir().getAbsolutePath() + "/../lib/" + libname); - Log.i("SDL", "libSDL: loading lib " + libpath.getAbsolutePath()); - System.load(libpath.getPath()); - } - catch( UnsatisfiedLinkError e ) - { - Log.i("SDL", "libSDL: error loading lib " + l + ": " + e.toString()); - try - { - String libname = System.mapLibraryName(l); - File libpath = new File(getFilesDir().getAbsolutePath() + "/" + libname); - Log.i("SDL", "libSDL: loading lib " + libpath.getAbsolutePath()); - System.load(libpath.getPath()); - } - catch( UnsatisfiedLinkError ee ) - { - Log.i("SDL", "libSDL: error loading lib " + l + ": " + ee.toString()); - System.loadLibrary(l); - } - } - } - } - catch ( UnsatisfiedLinkError e ) - { - try { - Log.i("SDL", "libSDL: Extracting APP2SD-ed libs"); - - InputStream in = null; - try - { - for( int i = 0; ; i++ ) - { - InputStream in2 = getAssets().open("bindata" + String.valueOf(i)); - if( in == null ) - in = in2; - else - in = new SequenceInputStream( in, in2 ); - } - } - catch( IOException ee ) { } - - if( in == null ) - throw new RuntimeException("libSDL: Extracting APP2SD-ed libs failed, the .apk file packaged incorrectly"); - - ZipInputStream zip = new ZipInputStream(in); - - File libDir = getFilesDir(); - try { - libDir.mkdirs(); - } catch( SecurityException ee ) { }; - - byte[] buf = new byte[16384]; - while(true) - { - ZipEntry entry = null; - entry = zip.getNextEntry(); - /* - if( entry != null ) - Log.i("SDL", "Extracting lib " + entry.getName()); - */ - /*if( entry == null ) - { - Log.i("SDL", "Extracting libs finished"); - break; - } - if( entry.isDirectory() ) - { - File outDir = new File( libDir.getAbsolutePath() + "/" + entry.getName() ); - if( !(outDir.exists() && outDir.isDirectory()) ) - outDir.mkdirs(); - continue; - } - - OutputStream out = null; - String path = libDir.getAbsolutePath() + "/" + entry.getName(); - try { - File outDir = new File( path.substring(0, path.lastIndexOf("/") )); - if( !(outDir.exists() && outDir.isDirectory()) ) - outDir.mkdirs(); - } catch( SecurityException eeeee ) { }; - - Log.i("SDL", "Saving to file '" + path + "'"); - - out = new FileOutputStream( path ); - int len = zip.read(buf); - while (len >= 0) - { - if(len > 0) - out.write(buf, 0, len); - len = zip.read(buf); - } - - out.flush(); - out.close(); - } - - for(String l : Globals.AppLibraries) - { - String libname = System.mapLibraryName(l); - File libpath = new File(libDir, libname); - Log.i("SDL", "libSDL: loading lib " + libpath.getPath()); - System.load(libpath.getPath()); - libpath.delete(); - } - } - catch ( Exception ee ) - { - Log.i("SDL", "libSDL: Error: " + ee.toString()); - } - } - - // ----- VCMI hack ----- - String [] binaryZipNames = { "binaries-" + android.os.Build.CPU_ABI + ".zip", "binaries.zip" }; - for(String binaryZip: binaryZipNames) - { - try { - Log.i("SDL", "libSDL: Trying to extract binaries from assets " + binaryZip); - - InputStream in = null; - try - { - for( int i = 0; ; i++ ) - { - InputStream in2 = getAssets().open(binaryZip + String.format("%02d", i)); - if( in == null ) - in = in2; - else - in = new SequenceInputStream( in, in2 ); - } - } - catch( IOException ee ) - { - try - { - if( in == null ) - in = getAssets().open(binaryZip); - } - catch( IOException eee ) {} - } - - if( in == null ) - throw new RuntimeException("libSDL: Extracting binaries failed, the .apk file packaged incorrectly"); - - ZipInputStream zip = new ZipInputStream(in); - - File libDir = getFilesDir(); - try { - libDir.mkdirs(); - } catch( SecurityException ee ) { }; - - byte[] buf = new byte[16384]; - while(true) - { - ZipEntry entry = null; - entry = zip.getNextEntry(); - /* - if( entry != null ) - Log.i("SDL", "Extracting lib " + entry.getName()); - */ - if( entry == null ) - { - Log.i("SDL", "Extracting binaries finished"); - break; - } - if( entry.isDirectory() ) - { - File outDir = new File( libDir.getAbsolutePath() + "/" + entry.getName() ); - if( !(outDir.exists() && outDir.isDirectory()) ) - outDir.mkdirs(); - continue; - } - - OutputStream out = null; - String path = libDir.getAbsolutePath() + "/" + entry.getName(); - try { - File outDir = new File( path.substring(0, path.lastIndexOf("/") )); - if( !(outDir.exists() && outDir.isDirectory()) ) - outDir.mkdirs(); - } catch( SecurityException eeeeeee ) { }; - - try { - CheckedInputStream check = new CheckedInputStream( new FileInputStream(path), new CRC32() ); - while( check.read(buf, 0, buf.length) > 0 ) {}; - check.close(); - if( check.getChecksum().getValue() != entry.getCrc() ) - { - File ff = new File(path); - ff.delete(); - throw new Exception(); - } - Log.i("SDL", "File '" + path + "' exists and passed CRC check - not overwriting it"); - continue; - } catch( Exception eeeeee ) { } - - Log.i("SDL", "Saving to file '" + path + "'"); - - out = new FileOutputStream( path ); - int len = zip.read(buf); - while (len >= 0) - { - if(len > 0) - out.write(buf, 0, len); - len = zip.read(buf); - } - - out.flush(); - out.close(); - Settings.nativeChmod(path, 0755); - - - //String chmod[] = { "/system/bin/chmod", "0755", path }; - //Runtime.getRuntime().exec(chmod).waitFor(); - } - } - catch ( Exception eee ) - { - //Log.i("SDL", "libSDL: Error: " + eee.toString()); - } - } - // ----- VCMI hack ----- - }; - - public static void LoadApplicationLibrary(final Context context) - { - Settings.nativeChdir(Globals.DataDir); - for(String l : Globals.AppMainLibraries) - { - try - { - String libname = System.mapLibraryName(l); - File libpath = new File(context.getFilesDir().getAbsolutePath() + "/../lib/" + libname); - Log.i("SDL", "libSDL: loading lib " + libpath.getAbsolutePath()); - System.load(libpath.getPath()); - } - catch( UnsatisfiedLinkError e ) - { - Log.i("SDL", "libSDL: error loading lib " + l + ": " + e.toString()); - try - { - String libname = System.mapLibraryName(l); - File libpath = new File(context.getFilesDir().getAbsolutePath() + "/" + libname); - Log.i("SDL", "libSDL: loading lib " + libpath.getAbsolutePath()); - System.load(libpath.getPath()); - } - catch( UnsatisfiedLinkError ee ) - { - Log.i("SDL", "libSDL: error loading lib " + l + ": " + ee.toString()); - System.loadLibrary(l); - } - } - } - } - - public int getApplicationVersion() - { - try { - PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); - return packageInfo.versionCode; - } catch (PackageManager.NameNotFoundException e) { - Log.i("SDL", "libSDL: Cannot get the version of our own package: " + e); - } - return 0; - } - - public boolean isRunningOnOUYA() - { - try { - PackageInfo packageInfo = getPackageManager().getPackageInfo("tv.ouya", 0); - return true; - } catch (PackageManager.NameNotFoundException e) { - } - return Globals.OuyaEmulation; - } - - public boolean isCurrentOrientationHorizontal() - { - Display getOrient = getWindowManager().getDefaultDisplay(); - return getOrient.getWidth() >= getOrient.getHeight(); - } - - public ViewGroup getVideoLayout() { return mLayout; } - - static int NOTIFY_ID = 12367098; // Random ID - -// private static DemoGLSurfaceView mGLView = null; - private static AudioThread mAudioThread = null; - private static DataDownloader downloader = null; - - private TextView _tv = null; - private Button _btn = null; - private LinearLayout _layout = null; - private LinearLayout _layout2 = null; - private Advertisement _ad = null; - - //private FrameLayout _videoLayout = null; - private EditText _screenKeyboard = null; - private String _screenKeyboardHintMessage = null; - private boolean sdlInited = false; - - public interface TouchEventsListener - { - public void onTouchEvent(final MotionEvent ev); - } - - public interface KeyEventsListener - { - public void onKeyEvent(final int keyCode); - } - - public TouchEventsListener touchListener = null; - public KeyEventsListener keyListener = null; - boolean _isPaused = false; - private InputMethodManager _inputManager = null; - - public LinkedList textInput = new LinkedList (); - public static MainActivity instance = null; -} - -// *** HONEYCOMB / ICS FIX FOR FULLSCREEN MODE, by lmak *** -abstract class DimSystemStatusBar -{ - public static DimSystemStatusBar get() - { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) - return DimSystemStatusBarHoneycomb.Holder.sInstance; - else - return DimSystemStatusBarDummy.Holder.sInstance; - } - public abstract void dim(final View view); - - private static class DimSystemStatusBarHoneycomb extends DimSystemStatusBar - { - private static class Holder - { - private static final DimSystemStatusBarHoneycomb sInstance = new DimSystemStatusBarHoneycomb(); - } - public void dim(final View view) - { - /* - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - // ICS has the same constant redefined with a different name. - hiddenStatusCode = android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE; - } - */ - view.setSystemUiVisibility(android.view.View.STATUS_BAR_HIDDEN); - } - } - private static class DimSystemStatusBarDummy extends DimSystemStatusBar - { - private static class Holder - { - private static final DimSystemStatusBarDummy sInstance = new DimSystemStatusBarDummy(); - } - public void dim(final View view) - { - } - } -} - -abstract class SetLayerType -{ - public static SetLayerType get() - { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) - return SetLayerTypeHoneycomb.Holder.sInstance; - else - return SetLayerTypeDummy.Holder.sInstance; - } - public abstract void setLayerType(final View view); - - private static class SetLayerTypeHoneycomb extends SetLayerType - { - private static class Holder - { - private static final SetLayerTypeHoneycomb sInstance = new SetLayerTypeHoneycomb(); - } - public void setLayerType(final View view) - { - view.setLayerType(android.view.View.LAYER_TYPE_NONE, null); - //view.setLayerType(android.view.View.LAYER_TYPE_HARDWARE, null); - } - } - private static class SetLayerTypeDummy extends SetLayerType - { - private static class Holder - { - private static final SetLayerTypeDummy sInstance = new SetLayerTypeDummy(); - } - public void setLayerType(final View view) - { - } - } -} diff --git a/project/javaSDL2/SDL.java b/project/javaSDL2/SDL.java new file mode 100644 index 000000000..fb7f7319a --- /dev/null +++ b/project/javaSDL2/SDL.java @@ -0,0 +1,84 @@ +package org.libsdl.app; + +import android.content.Context; + +import java.lang.reflect.*; + +/** + 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; + } + + public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException { + + if (libraryName == null) { + throw new NullPointerException("No library name provided."); + } + + try { + // Let's see if we have ReLinker available in the project. This is necessary for + // some projects that have huge numbers of local libraries bundled, and thus may + // trip a bug in Android's native library loader which ReLinker works around. (If + // loadLibrary works properly, ReLinker will simply use the normal Android method + // internally.) + // + // To use ReLinker, just add it as a dependency. For more information, see + // https://github.com/KeepSafe/ReLinker for ReLinker's repository. + // + Class relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); + Class relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); + Class contextClass = mContext.getClassLoader().loadClass("android.content.Context"); + Class stringClass = mContext.getClassLoader().loadClass("java.lang.String"); + + // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if + // they've changed during updates. + Method forceMethod = relinkClass.getDeclaredMethod("force"); + Object relinkInstance = forceMethod.invoke(null); + Class relinkInstanceClass = relinkInstance.getClass(); + + // Actually load the library! + Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass); + loadMethod.invoke(relinkInstance, mContext, libraryName, null, null); + } + catch (final Throwable e) { + // Fall back + try { + System.loadLibrary(libraryName); + } + catch (final UnsatisfiedLinkError ule) { + throw ule; + } + catch (final SecurityException se) { + throw se; + } + } + } + + protected static Context mContext; +} diff --git a/project/javaSDL2/SDLActivity.java b/project/javaSDL2/SDLActivity.java new file mode 100644 index 000000000..443739c14 --- /dev/null +++ b/project/javaSDL2/SDLActivity.java @@ -0,0 +1,2326 @@ +package org.libsdl.app; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Hashtable; +import java.lang.reflect.Method; +import java.lang.Math; + +import android.app.*; +import android.content.*; +import android.content.res.Configuration; +import android.text.InputType; +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.RelativeLayout; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.os.*; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.SparseArray; +import android.graphics.*; +import android.graphics.drawable.Drawable; +import android.hardware.*; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ApplicationInfo; + +/** + SDL Activity +*/ +public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { + private static final String TAG = "SDL"; + + public static boolean mIsResumedCalled, mHasFocus; + public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24); + + // Cursor types + private static final int SDL_SYSTEM_CURSOR_NONE = -1; + private static final int SDL_SYSTEM_CURSOR_ARROW = 0; + private static final int SDL_SYSTEM_CURSOR_IBEAM = 1; + private static final int SDL_SYSTEM_CURSOR_WAIT = 2; + private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3; + private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4; + private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5; + private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6; + private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7; + private static final int SDL_SYSTEM_CURSOR_SIZENS = 8; + private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9; + private static final int SDL_SYSTEM_CURSOR_NO = 10; + private static final int SDL_SYSTEM_CURSOR_HAND = 11; + + protected static final int SDL_ORIENTATION_UNKNOWN = 0; + protected static final int SDL_ORIENTATION_LANDSCAPE = 1; + protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2; + protected static final int SDL_ORIENTATION_PORTRAIT = 3; + protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4; + + protected static int mCurrentOrientation; + + // Handle the state of the native layer + public enum NativeState { + INIT, RESUMED, PAUSED + } + + public static NativeState mNextNativeState; + public static NativeState mCurrentNativeState; + + /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ + public static boolean mBrokenLibraries; + + // Main components + protected static SDLActivity mSingleton; + protected static SDLSurface mSurface; + protected static View mTextEdit; + protected static boolean mScreenKeyboardShown; + protected static ViewGroup mLayout; + protected static SDLClipboardHandler mClipboardHandler; + protected static Hashtable mCursors; + protected static int mLastCursorID; + protected static SDLGenericMotionListener_API12 mMotionListener; + protected static HIDDeviceManager mHIDDeviceManager; + + // This is what SDL runs in. It invokes SDL_main(), eventually + protected static Thread mSDLThread; + + protected static SDLGenericMotionListener_API12 getMotionListener() { + if (mMotionListener == null) { + if (Build.VERSION.SDK_INT >= 26) { + mMotionListener = new SDLGenericMotionListener_API26(); + } else + if (Build.VERSION.SDK_INT >= 24) { + mMotionListener = new SDLGenericMotionListener_API24(); + } else { + mMotionListener = new SDLGenericMotionListener_API12(); + } + } + + return mMotionListener; + } + + /** + * 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 getContext().getApplicationInfo().nativeLibraryDir + "/" + 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. + * 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[] { + "hidapi", + "SDL2", + // "SDL2_image", + // "SDL2_mixer", + // "SDL2_net", + // "SDL2_ttf", + "main" + }; + } + + // Load the .so + public void loadLibraries() { + for (String lib : getLibraries()) { + SDL.loadLibrary(lib); + } + } + + /** + * 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]; + } + + 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; + mClipboardHandler = null; + mCursors = new Hashtable(); + mLastCursorID = 0; + mSDLThread = null; + mBrokenLibraries = false; + mIsResumedCalled = false; + mHasFocus = true; + mNextNativeState = NativeState.INIT; + mCurrentNativeState = NativeState.INIT; + } + + // Setup + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.v(TAG, "Device: " + Build.DEVICE); + Log.v(TAG, "Model: " + Build.MODEL); + Log.v(TAG, "onCreate()"); + super.onCreate(savedInstanceState); + + try { + Thread.currentThread().setName("SDLActivity"); + } catch (Exception e) { + Log.v(TAG, "modify thread properties failed " + e.toString()); + } + + // 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) + { + mSingleton = this; + 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 JNI + SDL.setupJNI(); + + // Initialize state + SDL.initialize(); + + // So we can call stuff from static callbacks + mSingleton = this; + SDL.setContext(this); + + mClipboardHandler = new SDLClipboardHandler_API11(); + + mHIDDeviceManager = HIDDeviceManager.acquire(this); + + // Set up the surface + mSurface = new SDLSurface(getApplication()); + + mLayout = new RelativeLayout(this); + mLayout.addView(mSurface); + + // Get our current screen orientation and pass it down. + mCurrentOrientation = SDLActivity.getCurrentOrientation(); + // Only record current orientation + SDLActivity.onNativeOrientationChanged(mCurrentOrientation); + + setContentView(mLayout); + + setWindowStyle(false); + + getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this); + + // 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) { + Log.v(TAG, "Got filename: " + filename); + SDLActivity.onNativeDropFile(filename); + } + } + } + + protected void pauseNativeThread() { + mNextNativeState = NativeState.PAUSED; + mIsResumedCalled = false; + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handleNativeState(); + } + + protected void resumeNativeThread() { + mNextNativeState = NativeState.RESUMED; + mIsResumedCalled = true; + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handleNativeState(); + } + + // Events + @Override + protected void onPause() { + Log.v(TAG, "onPause()"); + super.onPause(); + + if (mHIDDeviceManager != null) { + mHIDDeviceManager.setFrozen(true); + } + if (!mHasMultiWindow) { + pauseNativeThread(); + } + } + + @Override + protected void onResume() { + Log.v(TAG, "onResume()"); + super.onResume(); + + if (mHIDDeviceManager != null) { + mHIDDeviceManager.setFrozen(false); + } + if (!mHasMultiWindow) { + resumeNativeThread(); + } + } + + @Override + protected void onStop() { + Log.v(TAG, "onStop()"); + super.onStop(); + if (mHasMultiWindow) { + pauseNativeThread(); + } + } + + @Override + protected void onStart() { + Log.v(TAG, "onStart()"); + super.onStart(); + if (mHasMultiWindow) { + resumeNativeThread(); + } + } + + public static int getCurrentOrientation() { + final Context context = SDLActivity.getContext(); + final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + + int result = SDL_ORIENTATION_UNKNOWN; + + switch (display.getRotation()) { + case Surface.ROTATION_0: + result = SDL_ORIENTATION_PORTRAIT; + break; + + case Surface.ROTATION_90: + result = SDL_ORIENTATION_LANDSCAPE; + break; + + case Surface.ROTATION_180: + result = SDL_ORIENTATION_PORTRAIT_FLIPPED; + break; + + case Surface.ROTATION_270: + result = SDL_ORIENTATION_LANDSCAPE_FLIPPED; + break; + } + + return result; + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + Log.v(TAG, "onWindowFocusChanged(): " + hasFocus); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + mHasFocus = hasFocus; + if (hasFocus) { + mNextNativeState = NativeState.RESUMED; + SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded(); + + SDLActivity.handleNativeState(); + nativeFocusChanged(true); + + } else { + nativeFocusChanged(false); + if (!mHasMultiWindow) { + mNextNativeState = NativeState.PAUSED; + SDLActivity.handleNativeState(); + } + } + } + + @Override + public void onLowMemory() { + Log.v(TAG, "onLowMemory()"); + super.onLowMemory(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.nativeLowMemory(); + } + + @Override + protected void onDestroy() { + Log.v(TAG, "onDestroy()"); + + if (mHIDDeviceManager != null) { + HIDDeviceManager.release(mHIDDeviceManager); + mHIDDeviceManager = null; + } + + if (SDLActivity.mBrokenLibraries) { + super.onDestroy(); + return; + } + + if (SDLActivity.mSDLThread != null) { + + // Send Quit event to "SDLThread" thread + SDLActivity.nativeSendQuit(); + + // Wait for "SDLThread" thread to end + try { + SDLActivity.mSDLThread.join(); + } catch(Exception e) { + Log.v(TAG, "Problem stopping SDLThread: " + e); + } + } + + SDLActivity.nativeQuit(); + + super.onDestroy(); + } + + @Override + public void onBackPressed() { + // Check if we want to block the back button in case of mouse right click. + // + // If we do, the normal hardware back button will no longer work and people have to use home, + // but the mouse right click will work. + // + String trapBack = SDLActivity.nativeGetHint("SDL_ANDROID_TRAP_BACK_BUTTON"); + if ((trapBack != null) && trapBack.equals("1")) { + // Exit and let the mouse handler handle this button (if appropriate) + return; + } + + // Default system back button behavior. + if (!isFinishing()) { + super.onBackPressed(); + } + } + + // Called by JNI from SDL. + public static void manualBackButton() { + mSingleton.pressBackButton(); + } + + // Used to get us onto the activity's main thread + public void pressBackButton() { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (!SDLActivity.this.isFinishing()) { + SDLActivity.this.superOnBackPressed(); + } + } + }); + } + + // Used to access the system back behavior. + public void superOnBackPressed() { + super.onBackPressed(); + } + + @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 == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */ + keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */ + ) { + return false; + } + return super.dispatchKeyEvent(event); + } + + /* Transition to next state */ + public static void handleNativeState() { + + if (mNextNativeState == mCurrentNativeState) { + // Already in same state, discard. + return; + } + + // Try a transition to init state + if (mNextNativeState == NativeState.INIT) { + + mCurrentNativeState = mNextNativeState; + return; + } + + // Try a transition to paused state + if (mNextNativeState == NativeState.PAUSED) { + if (mSDLThread != null) { + nativePause(); + } + if (mSurface != null) { + mSurface.handlePause(); + } + mCurrentNativeState = mNextNativeState; + return; + } + + // Try a transition to resumed state + if (mNextNativeState == NativeState.RESUMED) { + if (mSurface.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? + + mSDLThread = new Thread(new SDLMain(), "SDLThread"); + mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); + mSDLThread.start(); + + // No nativeResume(), don't signal Android_ResumeSem + mSurface.handleResume(); + } else { + nativeResume(); + mSurface.handleResume(); + } + + mCurrentNativeState = mNextNativeState; + } + } + } + + // Messages from the SDLMain thread + static final int COMMAND_CHANGE_TITLE = 1; + static final int COMMAND_CHANGE_WINDOW_STYLE = 2; + static final int COMMAND_TEXTEDIT_HIDE = 3; + static final int COMMAND_CHANGE_SURFACEVIEW_FORMAT = 4; + static final int COMMAND_SET_KEEP_SCREEN_ON = 5; + + protected static final int COMMAND_USER = 0x8000; + + protected static boolean mFullscreenModeActive; + + /** + * 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 = SDL.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_CHANGE_WINDOW_STYLE: + if (Build.VERSION.SDK_INT < 19) { + // This version of Android doesn't support the immersive fullscreen mode + break; + } + if (context instanceof Activity) { + Window window = ((Activity) context).getWindow(); + if (window != null) { + if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { + int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE; + window.getDecorView().setSystemUiVisibility(flags); + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + SDLActivity.mFullscreenModeActive = true; + } else { + int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE; + window.getDecorView().setSystemUiVisibility(flags); + window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + SDLActivity.mFullscreenModeActive = false; + } + } + } else { + Log.e(TAG, "error handling message, getContext() returned no Activity"); + } + break; + case COMMAND_TEXTEDIT_HIDE: + if (mTextEdit != null) { + // Note: On some devices setting view to GONE creates a flicker in landscape. + // Setting the View's sizes to 0 is similar to GONE but without the flicker. + // The sizes will be set to useful values when the keyboard is shown again. + mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0)); + + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); + + mScreenKeyboardShown = false; + + mSurface.requestFocus(); + } + break; + case COMMAND_SET_KEEP_SCREEN_ON: + { + if (context instanceof Activity) { + Window window = ((Activity) context).getWindow(); + if (window != null) { + if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + } + break; + } + case COMMAND_CHANGE_SURFACEVIEW_FORMAT: + { + int format = (Integer) msg.obj; + int pf; + + if (SDLActivity.mSurface == null) { + return; + } + + SurfaceHolder holder = SDLActivity.mSurface.getHolder(); + if (holder == null) { + return; + } + + if (format == 1) { + pf = PixelFormat.RGBA_8888; + } else if (format == 2) { + pf = PixelFormat.RGBX_8888; + } else { + pf = PixelFormat.RGB_565; + } + + holder.setFormat(pf); + + 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; + boolean result = commandHandler.sendMessage(msg); + + if ((Build.VERSION.SDK_INT >= 19) && (command == COMMAND_CHANGE_WINDOW_STYLE)) { + // Ensure we don't return until the resize has actually happened, + // or 500ms have passed. + + boolean bShouldWait = false; + + if (data instanceof Integer) { + // Let's figure out if we're already laid out fullscreen or not. + Display display = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + android.util.DisplayMetrics realMetrics = new android.util.DisplayMetrics(); + display.getRealMetrics( realMetrics ); + + boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) && + (realMetrics.heightPixels == mSurface.getHeight())); + + if (((Integer)data).intValue() == 1) { + // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going + // to change size and should wait for surfaceChanged() before we return, so the size + // is right back in native code. If we're already laid out fullscreen, though, we're + // not going to change size even if we change decor modes, so we shouldn't wait for + // surfaceChanged() -- which may not even happen -- and should return immediately. + bShouldWait = !bFullscreenLayout; + } + else { + // If we're laid out fullscreen (even if the status bar and nav bar are present), + // or are actively in fullscreen, we're going to change size and should wait for + // surfaceChanged before we return, so the size is right back in native code. + bShouldWait = bFullscreenLayout; + } + } + + if (bShouldWait && (SDLActivity.getContext() != null)) { + // We'll wait for the surfaceChanged() method, which will notify us + // when called. That way, we know our current size is really the + // size we need, instead of grabbing a size that's still got + // the navigation and/or status bars before they're hidden. + // + // We'll wait for up to half a second, because some devices + // take a surprisingly long time for the surface resize, but + // then we'll just give up and return. + // + synchronized(SDLActivity.getContext()) { + try { + SDLActivity.getContext().wait(500); + } + catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } + } + + return result; + } + + // C functions we call + public static native int nativeSetupJNI(); + public static native int nativeRunMain(String library, String function, Object arguments); + public static native void nativeLowMemory(); + public static native void nativeSendQuit(); + public static native void nativeQuit(); + public static native void nativePause(); + public static native void nativeResume(); + public static native void nativeFocusChanged(boolean hasFocus); + public static native void onNativeDropFile(String filename); + public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, int format, float rate); + public static native void onNativeResize(); + public static native void onNativeKeyDown(int keycode); + public static native void onNativeKeyUp(int keycode); + public static native boolean onNativeSoftReturnKey(); + public static native void onNativeKeyboardFocusLost(); + public static native void onNativeMouse(int button, int action, float x, float y, boolean relative); + public static native void onNativeTouch(int touchDevId, int pointerFingerId, + int action, float x, + float y, float p); + public static native void onNativeAccel(float x, float y, float z); + public static native void onNativeClipboardChanged(); + public static native void onNativeSurfaceCreated(); + public static native void onNativeSurfaceChanged(); + public static native void onNativeSurfaceDestroyed(); + public static native String nativeGetHint(String name); + public static native void nativeSetenv(String name, String value); + public static native void onNativeOrientationChanged(int orientation); + public static native void nativeAddTouch(int touchId, String name); + public static native void nativePermissionResult(int requestCode, boolean result); + + /** + * 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 void setWindowStyle(boolean fullscreen) { + // Called from SDLMain() thread and can't directly affect the view + mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0); + } + + /** + * 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_landscape = -1; + int orientation_portrait = -1; + + /* If set, hint "explicitly controls which UI orientations are allowed". */ + if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) { + orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; + } else if (hint.contains("LandscapeRight")) { + orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + } else if (hint.contains("LandscapeLeft")) { + orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + } + + if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) { + orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; + } else if (hint.contains("Portrait")) { + orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + } else if (hint.contains("PortraitUpsideDown")) { + orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; + } + + boolean is_landscape_allowed = (orientation_landscape == -1 ? false : true); + boolean is_portrait_allowed = (orientation_portrait == -1 ? false : true); + int req = -1; /* Requested orientation */ + + /* No valid hint, nothing is explicitly allowed */ + if (!is_portrait_allowed && !is_landscape_allowed) { + if (resizable) { + /* All orientations are allowed */ + req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; + } else { + /* Fixed window and nothing specified. Get orientation from w/h of created window */ + req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); + } + } else { + /* At least one orientation is allowed */ + if (resizable) { + if (is_portrait_allowed && is_landscape_allowed) { + /* hint allows both landscape and portrait, promote to full sensor */ + req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; + } else { + /* Use the only one allowed "orientation" */ + req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); + } + } else { + /* Fixed window and both orientations are allowed. Choose one. */ + if (is_portrait_allowed && is_landscape_allowed) { + req = (w > h ? orientation_landscape : orientation_portrait); + } else { + /* Use the only one allowed "orientation" */ + req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); + } + } + } + + Log.v("SDL", "setOrientation() requestedOrientation=" + req + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); + mSingleton.setRequestedOrientation(req); + } + + /** + * This method is called by SDL using JNI. + */ + public static void minimizeWindow() { + + if (mSingleton == null) { + return; + } + + Intent startMain = new Intent(Intent.ACTION_MAIN); + startMain.addCategory(Intent.CATEGORY_HOME); + startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mSingleton.startActivity(startMain); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean shouldMinimizeOnFocusLoss() { +/* + if (Build.VERSION.SDK_INT >= 24) { + if (mSingleton == null) { + return true; + } + + if (mSingleton.isInMultiWindowMode()) { + return false; + } + + if (mSingleton.isInPictureInPictureMode()) { + return false; + } + } + + return true; +*/ + return false; + } + + /** + * 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 supportsRelativeMouse() + { + // ChromeOS doesn't provide relative mouse motion via the Android 7 APIs + if (isChromebook()) { + return false; + } + + // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under + // Android 7 APIs, and simply returns no data under Android 8 APIs. + // + // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and + // thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result, + // we should stick to relative mode. + // + if ((Build.VERSION.SDK_INT < 27) && isDeXMode()) { + return false; + } + + return SDLActivity.getMotionListener().supportsRelativeMouse(); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean setRelativeMouseEnabled(boolean enabled) + { + if (enabled && !supportsRelativeMouse()) { + return false; + } + + return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled); + } + + /** + * 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)); + } + + /** + * This method is called by SDL using JNI. + */ + public static Context getContext() { + return SDL.getContext(); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isAndroidTV() { + UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE); + if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { + return true; + } + if (Build.MANUFACTURER.equals("MINIX") && Build.MODEL.equals("NEO-U1")) { + return true; + } + if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.equals("X96-W")) { + return true; + } + if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.startsWith("TV")) { + return true; + } + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isTablet() { + DisplayMetrics metrics = new DisplayMetrics(); + Activity activity = (Activity)getContext(); + if (activity == null) { + return false; + } + activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); + + double dWidthInches = metrics.widthPixels / (double)metrics.xdpi; + double dHeightInches = metrics.heightPixels / (double)metrics.ydpi; + + double dDiagonal = Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches)); + + // If our diagonal size is seven inches or greater, we consider ourselves a tablet. + return (dDiagonal >= 7.0); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isChromebook() { + if (getContext() == null) { + return false; + } + return getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isDeXMode() { + if (Build.VERSION.SDK_INT < 24) { + return false; + } + try { + final Configuration config = getContext().getResources().getConfiguration(); + final Class configClass = config.getClass(); + return configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass) + == configClass.getField("semDesktopModeEnabled").getInt(config); + } catch(Exception ignored) { + return false; + } + } + + /** + * This method is called by SDL using JNI. + */ + public static DisplayMetrics getDisplayDPI() { + return getContext().getResources().getDisplayMetrics(); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean getManifestEnvironmentVariables() { + try { + ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA); + Bundle bundle = applicationInfo.metaData; + if (bundle == null) { + return false; + } + String prefix = "SDL_ENV."; + final int trimLength = prefix.length(); + for (String key : bundle.keySet()) { + if (key.startsWith(prefix)) { + String name = key.substring(trimLength); + String value = bundle.get(key).toString(); + nativeSetenv(name, value); + } + } + /* environment variables set! */ + return true; + } catch (Exception e) { + Log.v("SDL", "exception " + e.toString()); + } + return false; + } + + // This method is called by SDLControllerManager's API 26 Generic Motion Handler. + public static View getContentView() + { + return mSingleton.mLayout; + } + + 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; + + /* Minimum size of 1 pixel, so it takes focus. */ + if (this.w <= 0) { + this.w = 1; + } + if (this.h + HEIGHT_PADDING <= 0) { + this.h = 1 - HEIGHT_PADDING; + } + } + + @Override + public void run() { + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING); + params.leftMargin = x; + params.topMargin = y; + + if (mTextEdit == null) { + mTextEdit = new DummyEdit(SDL.getContext()); + + mLayout.addView(mTextEdit, params); + } else { + mTextEdit.setLayoutParams(params); + } + + mTextEdit.setVisibility(View.VISIBLE); + mTextEdit.requestFocus(); + + InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mTextEdit, 0); + + mScreenKeyboardShown = true; + } + } + + /** + * 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)); + } + + public static boolean isTextInputEvent(KeyEvent event) { + + // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT + 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(); + } + + /** + * This method is called by SDL using JNI. + */ + public static void setSurfaceViewFormat(int format) { + mSingleton.sendCommand(COMMAND_CHANGE_SURFACEVIEW_FORMAT, format); + return; + } + + // Input + + /** + * This method is called by SDL using JNI. + */ + public static void initTouch() { + int[] ids = InputDevice.getDeviceIds(); + + for (int i = 0; i < ids.length; ++i) { + InputDevice device = InputDevice.getDevice(ids[i]); + if (device != null && (device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) != 0) { + nativeAddTouch(device.getId(), device.getName()); + } + } + } + + // APK expansion files support + + /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */ + private static Object expansionFile; + + /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */ + 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 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"); + if (mainHint == null) { + return null; // no expansion use if no main version was set + } + String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION"); + if (patchHint == null) { + return null; // no expansion use if no patch version was set + } + + Integer mainVersion; + Integer patchVersion; + try { + mainVersion = Integer.valueOf(mainHint); + patchVersion = Integer.valueOf(patchHint); + } catch (NumberFormatException ex) { + ex.printStackTrace(); + throw new IOException("No valid file versions set for APK expansion files", ex); + } + + try { + // To avoid direct dependency on Google APK expansion 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, SDL.getContext(), mainVersion, patchVersion); + + expansionFileMethod = expansionFile.getClass() + .getMethod("getInputStream", String.class); + } catch (Exception ex) { + ex.printStackTrace(); + expansionFile = null; + expansionFileMethod = null; + throw new IOException("Could not access APK expansion support library", ex); + } + } + + // 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) { + // calling "getInputStream" failed + ex.printStackTrace(); + throw new IOException("Could not open stream from APK expansion file", ex); + } + + if (fileStream == null) { + // calling "getInputStream" was successful but null was returned + throw new IOException("Could not find path in APK expansion file"); + } + + 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