cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

SDLActivity.java (51566B)


      1package org.libsdl.app;
      2
      3import java.io.IOException;
      4import java.io.InputStream;
      5import java.util.ArrayList;
      6import java.util.Arrays;
      7import java.util.Collections;
      8import java.util.Comparator;
      9import java.util.List;
     10import java.lang.reflect.Method;
     11
     12import android.app.*;
     13import android.content.*;
     14import android.view.*;
     15import android.view.inputmethod.BaseInputConnection;
     16import android.view.inputmethod.EditorInfo;
     17import android.view.inputmethod.InputConnection;
     18import android.view.inputmethod.InputMethodManager;
     19import android.widget.AbsoluteLayout;
     20import android.widget.Button;
     21import android.widget.LinearLayout;
     22import android.widget.TextView;
     23import android.os.*;
     24import android.util.Log;
     25import android.util.SparseArray;
     26import android.graphics.*;
     27import android.graphics.drawable.Drawable;
     28import android.media.*;
     29import android.hardware.*;
     30
     31/**
     32    SDL Activity
     33*/
     34public class SDLActivity extends Activity {
     35    private static final String TAG = "SDL";
     36
     37    // Keep track of the paused state
     38    public static boolean mIsPaused, mIsSurfaceReady, mHasFocus;
     39    public static boolean mExitCalledFromJava;
     40
     41    // Main components
     42    protected static SDLActivity mSingleton;
     43    protected static SDLSurface mSurface;
     44    protected static View mTextEdit;
     45    protected static ViewGroup mLayout;
     46    protected static SDLJoystickHandler mJoystickHandler;
     47
     48    // This is what SDL runs in. It invokes SDL_main(), eventually
     49    protected static Thread mSDLThread;
     50    
     51    // Audio
     52    protected static AudioTrack mAudioTrack;
     53
     54    // Load the .so
     55    static {
     56        System.loadLibrary("SDL2");
     57        //System.loadLibrary("SDL2_image");
     58        //System.loadLibrary("SDL2_mixer");
     59        //System.loadLibrary("SDL2_net");
     60        //System.loadLibrary("SDL2_ttf");
     61        System.loadLibrary("main");
     62    }
     63    
     64    /**
     65     * This method is called by SDL using JNI.
     66     * This method is called by SDL before starting the native application thread.
     67     * It can be overridden to provide the arguments after the application name.
     68     * The default implementation returns an empty array. It never returns null.
     69     * @return arguments for the native application.
     70     */
     71    protected String[] getArguments() {
     72        return new String[0];
     73    }
     74    
     75    public static void initialize() {
     76        // The static nature of the singleton and Android quirkyness force us to initialize everything here
     77        // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
     78        mSingleton = null;
     79        mSurface = null;
     80        mTextEdit = null;
     81        mLayout = null;
     82        mJoystickHandler = null;
     83        mSDLThread = null;
     84        mAudioTrack = null;
     85        mExitCalledFromJava = false;
     86        mIsPaused = false;
     87        mIsSurfaceReady = false;
     88        mHasFocus = true;
     89    }
     90
     91    // Setup
     92    @Override
     93    protected void onCreate(Bundle savedInstanceState) {
     94        Log.v("SDL", "onCreate():" + mSingleton);
     95        super.onCreate(savedInstanceState);
     96        
     97        SDLActivity.initialize();
     98        // So we can call stuff from static callbacks
     99        mSingleton = this;
    100
    101        // Set up the surface
    102        mSurface = new SDLSurface(getApplication());
    103        
    104        if(Build.VERSION.SDK_INT >= 12) {
    105            mJoystickHandler = new SDLJoystickHandler_API12();
    106        }
    107        else {
    108            mJoystickHandler = new SDLJoystickHandler();
    109        }
    110
    111        mLayout = new AbsoluteLayout(this);
    112        mLayout.addView(mSurface);
    113
    114        setContentView(mLayout);
    115    }
    116
    117    // Events
    118    @Override
    119    protected void onPause() {
    120        Log.v("SDL", "onPause()");
    121        super.onPause();
    122        SDLActivity.handlePause();
    123    }
    124
    125    @Override
    126    protected void onResume() {
    127        Log.v("SDL", "onResume()");
    128        super.onResume();
    129        SDLActivity.handleResume();
    130    }
    131
    132
    133    @Override
    134    public void onWindowFocusChanged(boolean hasFocus) {
    135        super.onWindowFocusChanged(hasFocus);
    136        Log.v("SDL", "onWindowFocusChanged(): " + hasFocus);
    137
    138        SDLActivity.mHasFocus = hasFocus;
    139        if (hasFocus) {
    140            SDLActivity.handleResume();
    141        }
    142    }
    143
    144    @Override
    145    public void onLowMemory() {
    146        Log.v("SDL", "onLowMemory()");
    147        super.onLowMemory();
    148        SDLActivity.nativeLowMemory();
    149    }
    150
    151    @Override
    152    protected void onDestroy() {
    153        Log.v("SDL", "onDestroy()");
    154        // Send a quit message to the application
    155        SDLActivity.mExitCalledFromJava = true;
    156        SDLActivity.nativeQuit();
    157
    158        // Now wait for the SDL thread to quit
    159        if (SDLActivity.mSDLThread != null) {
    160            try {
    161                SDLActivity.mSDLThread.join();
    162            } catch(Exception e) {
    163                Log.v("SDL", "Problem stopping thread: " + e);
    164            }
    165            SDLActivity.mSDLThread = null;
    166
    167            //Log.v("SDL", "Finished waiting for SDL thread");
    168        }
    169            
    170        super.onDestroy();
    171        // Reset everything in case the user re opens the app
    172        SDLActivity.initialize();
    173    }
    174
    175    @Override
    176    public boolean dispatchKeyEvent(KeyEvent event) {
    177        int keyCode = event.getKeyCode();
    178        // Ignore certain special keys so they're handled by Android
    179        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
    180            keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
    181            keyCode == KeyEvent.KEYCODE_CAMERA ||
    182            keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */
    183            keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */
    184            ) {
    185            return false;
    186        }
    187        return super.dispatchKeyEvent(event);
    188    }
    189
    190    /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed
    191     *  is the first to be called, mIsSurfaceReady should still be set
    192     *  to 'true' during the call to onPause (in a usual scenario).
    193     */
    194    public static void handlePause() {
    195        if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) {
    196            SDLActivity.mIsPaused = true;
    197            SDLActivity.nativePause();
    198            mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, false);
    199        }
    200    }
    201
    202    /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready.
    203     * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume
    204     * every time we get one of those events, only if it comes after surfaceDestroyed
    205     */
    206    public static void handleResume() {
    207        if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) {
    208            SDLActivity.mIsPaused = false;
    209            SDLActivity.nativeResume();
    210            mSurface.handleResume();
    211        }
    212    }
    213        
    214    /* The native thread has finished */
    215    public static void handleNativeExit() {
    216        SDLActivity.mSDLThread = null;
    217        mSingleton.finish();
    218    }
    219
    220
    221    // Messages from the SDLMain thread
    222    static final int COMMAND_CHANGE_TITLE = 1;
    223    static final int COMMAND_UNUSED = 2;
    224    static final int COMMAND_TEXTEDIT_HIDE = 3;
    225    static final int COMMAND_SET_KEEP_SCREEN_ON = 5;
    226
    227    protected static final int COMMAND_USER = 0x8000;
    228
    229    /**
    230     * This method is called by SDL if SDL did not handle a message itself.
    231     * This happens if a received message contains an unsupported command.
    232     * Method can be overwritten to handle Messages in a different class.
    233     * @param command the command of the message.
    234     * @param param the parameter of the message. May be null.
    235     * @return if the message was handled in overridden method.
    236     */
    237    protected boolean onUnhandledMessage(int command, Object param) {
    238        return false;
    239    }
    240
    241    /**
    242     * A Handler class for Messages from native SDL applications.
    243     * It uses current Activities as target (e.g. for the title).
    244     * static to prevent implicit references to enclosing object.
    245     */
    246    protected static class SDLCommandHandler extends Handler {
    247        @Override
    248        public void handleMessage(Message msg) {
    249            Context context = getContext();
    250            if (context == null) {
    251                Log.e(TAG, "error handling message, getContext() returned null");
    252                return;
    253            }
    254            switch (msg.arg1) {
    255            case COMMAND_CHANGE_TITLE:
    256                if (context instanceof Activity) {
    257                    ((Activity) context).setTitle((String)msg.obj);
    258                } else {
    259                    Log.e(TAG, "error handling message, getContext() returned no Activity");
    260                }
    261                break;
    262            case COMMAND_TEXTEDIT_HIDE:
    263                if (mTextEdit != null) {
    264                    mTextEdit.setVisibility(View.GONE);
    265
    266                    InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
    267                    imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
    268                }
    269                break;
    270            case COMMAND_SET_KEEP_SCREEN_ON:
    271            {
    272                Window window = ((Activity) context).getWindow();
    273                if (window != null) {
    274                    if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
    275                        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    276                    } else {
    277                        window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    278                    }
    279                }
    280                break;
    281            }
    282            default:
    283                if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {
    284                    Log.e(TAG, "error handling message, command is " + msg.arg1);
    285                }
    286            }
    287        }
    288    }
    289
    290    // Handler for the messages
    291    Handler commandHandler = new SDLCommandHandler();
    292
    293    // Send a message from the SDLMain thread
    294    boolean sendCommand(int command, Object data) {
    295        Message msg = commandHandler.obtainMessage();
    296        msg.arg1 = command;
    297        msg.obj = data;
    298        return commandHandler.sendMessage(msg);
    299    }
    300
    301    // C functions we call
    302    public static native int nativeInit(Object arguments);
    303    public static native void nativeLowMemory();
    304    public static native void nativeQuit();
    305    public static native void nativePause();
    306    public static native void nativeResume();
    307    public static native void onNativeResize(int x, int y, int format);
    308    public static native int onNativePadDown(int device_id, int keycode);
    309    public static native int onNativePadUp(int device_id, int keycode);
    310    public static native void onNativeJoy(int device_id, int axis,
    311                                          float value);
    312    public static native void onNativeHat(int device_id, int hat_id,
    313                                          int x, int y);
    314    public static native void onNativeKeyDown(int keycode);
    315    public static native void onNativeKeyUp(int keycode);
    316    public static native void onNativeKeyboardFocusLost();
    317    public static native void onNativeTouch(int touchDevId, int pointerFingerId,
    318                                            int action, float x, 
    319                                            float y, float p);
    320    public static native void onNativeAccel(float x, float y, float z);
    321    public static native void onNativeSurfaceChanged();
    322    public static native void onNativeSurfaceDestroyed();
    323    public static native void nativeFlipBuffers();
    324    public static native int nativeAddJoystick(int device_id, String name, 
    325                                               int is_accelerometer, int nbuttons, 
    326                                               int naxes, int nhats, int nballs);
    327    public static native int nativeRemoveJoystick(int device_id);
    328    public static native String nativeGetHint(String name);
    329
    330    /**
    331     * This method is called by SDL using JNI.
    332     */
    333    public static void flipBuffers() {
    334        SDLActivity.nativeFlipBuffers();
    335    }
    336
    337    /**
    338     * This method is called by SDL using JNI.
    339     */
    340    public static boolean setActivityTitle(String title) {
    341        // Called from SDLMain() thread and can't directly affect the view
    342        return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
    343    }
    344
    345    /**
    346     * This method is called by SDL using JNI.
    347     */
    348    public static boolean sendMessage(int command, int param) {
    349        return mSingleton.sendCommand(command, Integer.valueOf(param));
    350    }
    351
    352    /**
    353     * This method is called by SDL using JNI.
    354     */
    355    public static Context getContext() {
    356        return mSingleton;
    357    }
    358
    359    /**
    360     * This method is called by SDL using JNI.
    361     * @return result of getSystemService(name) but executed on UI thread.
    362     */
    363    public Object getSystemServiceFromUiThread(final String name) {
    364        final Object lock = new Object();
    365        final Object[] results = new Object[2]; // array for writable variables
    366        synchronized (lock) {
    367            runOnUiThread(new Runnable() {
    368                @Override
    369                public void run() {
    370                    synchronized (lock) {
    371                        results[0] = getSystemService(name);
    372                        results[1] = Boolean.TRUE;
    373                        lock.notify();
    374                    }
    375                }
    376            });
    377            if (results[1] == null) {
    378                try {
    379                    lock.wait();
    380                } catch (InterruptedException ex) {
    381                    ex.printStackTrace();
    382                }
    383            }
    384        }
    385        return results[0];
    386    }
    387
    388    static class ShowTextInputTask implements Runnable {
    389        /*
    390         * This is used to regulate the pan&scan method to have some offset from
    391         * the bottom edge of the input region and the top edge of an input
    392         * method (soft keyboard)
    393         */
    394        static final int HEIGHT_PADDING = 15;
    395
    396        public int x, y, w, h;
    397
    398        public ShowTextInputTask(int x, int y, int w, int h) {
    399            this.x = x;
    400            this.y = y;
    401            this.w = w;
    402            this.h = h;
    403        }
    404
    405        @Override
    406        public void run() {
    407            AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams(
    408                    w, h + HEIGHT_PADDING, x, y);
    409
    410            if (mTextEdit == null) {
    411                mTextEdit = new DummyEdit(getContext());
    412
    413                mLayout.addView(mTextEdit, params);
    414            } else {
    415                mTextEdit.setLayoutParams(params);
    416            }
    417
    418            mTextEdit.setVisibility(View.VISIBLE);
    419            mTextEdit.requestFocus();
    420
    421            InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
    422            imm.showSoftInput(mTextEdit, 0);
    423        }
    424    }
    425
    426    /**
    427     * This method is called by SDL using JNI.
    428     */
    429    public static boolean showTextInput(int x, int y, int w, int h) {
    430        // Transfer the task to the main thread as a Runnable
    431        return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
    432    }
    433
    434    /**
    435     * This method is called by SDL using JNI.
    436     */
    437    public static Surface getNativeSurface() {
    438        return SDLActivity.mSurface.getNativeSurface();
    439    }
    440
    441    // Audio
    442
    443    /**
    444     * This method is called by SDL using JNI.
    445     */
    446    public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
    447        int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
    448        int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
    449        int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
    450        
    451        Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
    452        
    453        // Let the user pick a larger buffer if they really want -- but ye
    454        // gods they probably shouldn't, the minimums are horrifyingly high
    455        // latency already
    456        desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
    457        
    458        if (mAudioTrack == null) {
    459            mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
    460                    channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
    461            
    462            // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
    463            // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
    464            // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
    465            
    466            if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
    467                Log.e("SDL", "Failed during initialization of Audio Track");
    468                mAudioTrack = null;
    469                return -1;
    470            }
    471            
    472            mAudioTrack.play();
    473        }
    474       
    475        Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
    476        
    477        return 0;
    478    }
    479
    480    /**
    481     * This method is called by SDL using JNI.
    482     */
    483    public static void audioWriteShortBuffer(short[] buffer) {
    484        for (int i = 0; i < buffer.length; ) {
    485            int result = mAudioTrack.write(buffer, i, buffer.length - i);
    486            if (result > 0) {
    487                i += result;
    488            } else if (result == 0) {
    489                try {
    490                    Thread.sleep(1);
    491                } catch(InterruptedException e) {
    492                    // Nom nom
    493                }
    494            } else {
    495                Log.w("SDL", "SDL audio: error return from write(short)");
    496                return;
    497            }
    498        }
    499    }
    500
    501    /**
    502     * This method is called by SDL using JNI.
    503     */
    504    public static void audioWriteByteBuffer(byte[] buffer) {
    505        for (int i = 0; i < buffer.length; ) {
    506            int result = mAudioTrack.write(buffer, i, buffer.length - i);
    507            if (result > 0) {
    508                i += result;
    509            } else if (result == 0) {
    510                try {
    511                    Thread.sleep(1);
    512                } catch(InterruptedException e) {
    513                    // Nom nom
    514                }
    515            } else {
    516                Log.w("SDL", "SDL audio: error return from write(byte)");
    517                return;
    518            }
    519        }
    520    }
    521
    522    /**
    523     * This method is called by SDL using JNI.
    524     */
    525    public static void audioQuit() {
    526        if (mAudioTrack != null) {
    527            mAudioTrack.stop();
    528            mAudioTrack = null;
    529        }
    530    }
    531
    532    // Input
    533
    534    /**
    535     * This method is called by SDL using JNI.
    536     * @return an array which may be empty but is never null.
    537     */
    538    public static int[] inputGetInputDeviceIds(int sources) {
    539        int[] ids = InputDevice.getDeviceIds();
    540        int[] filtered = new int[ids.length];
    541        int used = 0;
    542        for (int i = 0; i < ids.length; ++i) {
    543            InputDevice device = InputDevice.getDevice(ids[i]);
    544            if ((device != null) && ((device.getSources() & sources) != 0)) {
    545                filtered[used++] = device.getId();
    546            }
    547        }
    548        return Arrays.copyOf(filtered, used);
    549    }
    550
    551    // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
    552    public static boolean handleJoystickMotionEvent(MotionEvent event) {
    553        return mJoystickHandler.handleMotionEvent(event);
    554    }
    555
    556    /**
    557     * This method is called by SDL using JNI.
    558     */
    559    public static void pollInputDevices() {
    560        if (SDLActivity.mSDLThread != null) {
    561            mJoystickHandler.pollInputDevices();
    562        }
    563    }
    564
    565    // APK extension files support
    566
    567    /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
    568    private Object expansionFile;
    569
    570    /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
    571    private Method expansionFileMethod;
    572
    573    /**
    574     * This method is called by SDL using JNI.
    575     */
    576    public InputStream openAPKExtensionInputStream(String fileName) throws IOException {
    577        // Get a ZipResourceFile representing a merger of both the main and patch files
    578        if (expansionFile == null) {
    579            Integer mainVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION"));
    580            Integer patchVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION"));
    581
    582            try {
    583                // To avoid direct dependency on Google APK extension library that is
    584                // not a part of Android SDK we access it using reflection
    585                expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport")
    586                    .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class)
    587                    .invoke(null, this, mainVersion, patchVersion);
    588
    589                expansionFileMethod = expansionFile.getClass()
    590                    .getMethod("getInputStream", String.class);
    591            } catch (Exception ex) {
    592                ex.printStackTrace();
    593                expansionFile = null;
    594                expansionFileMethod = null;
    595            }
    596        }
    597
    598        // Get an input stream for a known file inside the expansion file ZIPs
    599        InputStream fileStream;
    600        try {
    601            fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName);
    602        } catch (Exception ex) {
    603            ex.printStackTrace();
    604            fileStream = null;
    605        }
    606
    607        if (fileStream == null) {
    608            throw new IOException();
    609        }
    610
    611        return fileStream;
    612    }
    613
    614    // Messagebox
    615
    616    /** Result of current messagebox. Also used for blocking the calling thread. */
    617    protected final int[] messageboxSelection = new int[1];
    618
    619    /** Id of current dialog. */
    620    protected int dialogs = 0;
    621
    622    /**
    623     * This method is called by SDL using JNI.
    624     * Shows the messagebox from UI thread and block calling thread.
    625     * buttonFlags, buttonIds and buttonTexts must have same length.
    626     * @param buttonFlags array containing flags for every button.
    627     * @param buttonIds array containing id for every button.
    628     * @param buttonTexts array containing text for every button.
    629     * @param colors null for default or array of length 5 containing colors.
    630     * @return button id or -1.
    631     */
    632    public int messageboxShowMessageBox(
    633            final int flags,
    634            final String title,
    635            final String message,
    636            final int[] buttonFlags,
    637            final int[] buttonIds,
    638            final String[] buttonTexts,
    639            final int[] colors) {
    640
    641        messageboxSelection[0] = -1;
    642
    643        // sanity checks
    644
    645        if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {
    646            return -1; // implementation broken
    647        }
    648
    649        // collect arguments for Dialog
    650
    651        final Bundle args = new Bundle();
    652        args.putInt("flags", flags);
    653        args.putString("title", title);
    654        args.putString("message", message);
    655        args.putIntArray("buttonFlags", buttonFlags);
    656        args.putIntArray("buttonIds", buttonIds);
    657        args.putStringArray("buttonTexts", buttonTexts);
    658        args.putIntArray("colors", colors);
    659
    660        // trigger Dialog creation on UI thread
    661
    662        runOnUiThread(new Runnable() {
    663            @Override
    664            public void run() {
    665                showDialog(dialogs++, args);
    666            }
    667        });
    668
    669        // block the calling thread
    670
    671        synchronized (messageboxSelection) {
    672            try {
    673                messageboxSelection.wait();
    674            } catch (InterruptedException ex) {
    675                ex.printStackTrace();
    676                return -1;
    677            }
    678        }
    679
    680        // return selected value
    681
    682        return messageboxSelection[0];
    683    }
    684
    685    @Override
    686    protected Dialog onCreateDialog(int ignore, Bundle args) {
    687
    688        // TODO set values from "flags" to messagebox dialog
    689
    690        // get colors
    691
    692        int[] colors = args.getIntArray("colors");
    693        int backgroundColor;
    694        int textColor;
    695        int buttonBorderColor;
    696        int buttonBackgroundColor;
    697        int buttonSelectedColor;
    698        if (colors != null) {
    699            int i = -1;
    700            backgroundColor = colors[++i];
    701            textColor = colors[++i];
    702            buttonBorderColor = colors[++i];
    703            buttonBackgroundColor = colors[++i];
    704            buttonSelectedColor = colors[++i];
    705        } else {
    706            backgroundColor = Color.TRANSPARENT;
    707            textColor = Color.TRANSPARENT;
    708            buttonBorderColor = Color.TRANSPARENT;
    709            buttonBackgroundColor = Color.TRANSPARENT;
    710            buttonSelectedColor = Color.TRANSPARENT;
    711        }
    712
    713        // create dialog with title and a listener to wake up calling thread
    714
    715        final Dialog dialog = new Dialog(this);
    716        dialog.setTitle(args.getString("title"));
    717        dialog.setCancelable(false);
    718        dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
    719            @Override
    720            public void onDismiss(DialogInterface unused) {
    721                synchronized (messageboxSelection) {
    722                    messageboxSelection.notify();
    723                }
    724            }
    725        });
    726
    727        // create text
    728
    729        TextView message = new TextView(this);
    730        message.setGravity(Gravity.CENTER);
    731        message.setText(args.getString("message"));
    732        if (textColor != Color.TRANSPARENT) {
    733            message.setTextColor(textColor);
    734        }
    735
    736        // create buttons
    737
    738        int[] buttonFlags = args.getIntArray("buttonFlags");
    739        int[] buttonIds = args.getIntArray("buttonIds");
    740        String[] buttonTexts = args.getStringArray("buttonTexts");
    741
    742        final SparseArray<Button> mapping = new SparseArray<Button>();
    743
    744        LinearLayout buttons = new LinearLayout(this);
    745        buttons.setOrientation(LinearLayout.HORIZONTAL);
    746        buttons.setGravity(Gravity.CENTER);
    747        for (int i = 0; i < buttonTexts.length; ++i) {
    748            Button button = new Button(this);
    749            final int id = buttonIds[i];
    750            button.setOnClickListener(new View.OnClickListener() {
    751                @Override
    752                public void onClick(View v) {
    753                    messageboxSelection[0] = id;
    754                    dialog.dismiss();
    755                }
    756            });
    757            if (buttonFlags[i] != 0) {
    758                // see SDL_messagebox.h
    759                if ((buttonFlags[i] & 0x00000001) != 0) {
    760                    mapping.put(KeyEvent.KEYCODE_ENTER, button);
    761                }
    762                if ((buttonFlags[i] & 0x00000002) != 0) {
    763                    mapping.put(111, button); /* API 11: KeyEvent.KEYCODE_ESCAPE */
    764                }
    765            }
    766            button.setText(buttonTexts[i]);
    767            if (textColor != Color.TRANSPARENT) {
    768                button.setTextColor(textColor);
    769            }
    770            if (buttonBorderColor != Color.TRANSPARENT) {
    771                // TODO set color for border of messagebox button
    772            }
    773            if (buttonBackgroundColor != Color.TRANSPARENT) {
    774                Drawable drawable = button.getBackground();
    775                if (drawable == null) {
    776                    // setting the color this way removes the style
    777                    button.setBackgroundColor(buttonBackgroundColor);
    778                } else {
    779                    // setting the color this way keeps the style (gradient, padding, etc.)
    780                    drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY);
    781                }
    782            }
    783            if (buttonSelectedColor != Color.TRANSPARENT) {
    784                // TODO set color for selected messagebox button
    785            }
    786            buttons.addView(button);
    787        }
    788
    789        // create content
    790
    791        LinearLayout content = new LinearLayout(this);
    792        content.setOrientation(LinearLayout.VERTICAL);
    793        content.addView(message);
    794        content.addView(buttons);
    795        if (backgroundColor != Color.TRANSPARENT) {
    796            content.setBackgroundColor(backgroundColor);
    797        }
    798
    799        // add content to dialog and return
    800
    801        dialog.setContentView(content);
    802        dialog.setOnKeyListener(new Dialog.OnKeyListener() {
    803            @Override
    804            public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {
    805                Button button = mapping.get(keyCode);
    806                if (button != null) {
    807                    if (event.getAction() == KeyEvent.ACTION_UP) {
    808                        button.performClick();
    809                    }
    810                    return true; // also for ignored actions
    811                }
    812                return false;
    813            }
    814        });
    815
    816        return dialog;
    817    }
    818}
    819
    820/**
    821    Simple nativeInit() runnable
    822*/
    823class SDLMain implements Runnable {
    824    @Override
    825    public void run() {
    826        // Runs SDL_main()
    827        SDLActivity.nativeInit(SDLActivity.mSingleton.getArguments());
    828
    829        //Log.v("SDL", "SDL thread terminated");
    830    }
    831}
    832
    833
    834/**
    835    SDLSurface. This is what we draw on, so we need to know when it's created
    836    in order to do anything useful. 
    837
    838    Because of this, that's where we set up the SDL thread
    839*/
    840class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, 
    841    View.OnKeyListener, View.OnTouchListener, SensorEventListener  {
    842
    843    // Sensors
    844    protected static SensorManager mSensorManager;
    845    protected static Display mDisplay;
    846
    847    // Keep track of the surface size to normalize touch events
    848    protected static float mWidth, mHeight;
    849
    850    // Startup    
    851    public SDLSurface(Context context) {
    852        super(context);
    853        getHolder().addCallback(this); 
    854    
    855        setFocusable(true);
    856        setFocusableInTouchMode(true);
    857        requestFocus();
    858        setOnKeyListener(this); 
    859        setOnTouchListener(this);   
    860
    861        mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
    862        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
    863        
    864        if(Build.VERSION.SDK_INT >= 12) {
    865            setOnGenericMotionListener(new SDLGenericMotionListener_API12());
    866        }
    867
    868        // Some arbitrary defaults to avoid a potential division by zero
    869        mWidth = 1.0f;
    870        mHeight = 1.0f;
    871    }
    872     
    873    public void handleResume() {
    874        setFocusable(true);
    875        setFocusableInTouchMode(true);
    876        requestFocus();
    877        setOnKeyListener(this);
    878        setOnTouchListener(this);
    879        enableSensor(Sensor.TYPE_ACCELEROMETER, true);
    880    }
    881    
    882    public Surface getNativeSurface() {
    883        return getHolder().getSurface();
    884    }
    885
    886    // Called when we have a valid drawing surface
    887    @Override
    888    public void surfaceCreated(SurfaceHolder holder) {
    889        Log.v("SDL", "surfaceCreated()");
    890        holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
    891    }
    892
    893    // Called when we lose the surface
    894    @Override
    895    public void surfaceDestroyed(SurfaceHolder holder) {
    896        Log.v("SDL", "surfaceDestroyed()");
    897        // Call this *before* setting mIsSurfaceReady to 'false'
    898        SDLActivity.handlePause();
    899        SDLActivity.mIsSurfaceReady = false;
    900        SDLActivity.onNativeSurfaceDestroyed();
    901    }
    902
    903    // Called when the surface is resized
    904    @Override
    905    public void surfaceChanged(SurfaceHolder holder,
    906                               int format, int width, int height) {
    907        Log.v("SDL", "surfaceChanged()");
    908
    909        int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default
    910        switch (format) {
    911        case PixelFormat.A_8:
    912            Log.v("SDL", "pixel format A_8");
    913            break;
    914        case PixelFormat.LA_88:
    915            Log.v("SDL", "pixel format LA_88");
    916            break;
    917        case PixelFormat.L_8:
    918            Log.v("SDL", "pixel format L_8");
    919            break;
    920        case PixelFormat.RGBA_4444:
    921            Log.v("SDL", "pixel format RGBA_4444");
    922            sdlFormat = 0x15421002; // SDL_PIXELFORMAT_RGBA4444
    923            break;
    924        case PixelFormat.RGBA_5551:
    925            Log.v("SDL", "pixel format RGBA_5551");
    926            sdlFormat = 0x15441002; // SDL_PIXELFORMAT_RGBA5551
    927            break;
    928        case PixelFormat.RGBA_8888:
    929            Log.v("SDL", "pixel format RGBA_8888");
    930            sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888
    931            break;
    932        case PixelFormat.RGBX_8888:
    933            Log.v("SDL", "pixel format RGBX_8888");
    934            sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888
    935            break;
    936        case PixelFormat.RGB_332:
    937            Log.v("SDL", "pixel format RGB_332");
    938            sdlFormat = 0x14110801; // SDL_PIXELFORMAT_RGB332
    939            break;
    940        case PixelFormat.RGB_565:
    941            Log.v("SDL", "pixel format RGB_565");
    942            sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565
    943            break;
    944        case PixelFormat.RGB_888:
    945            Log.v("SDL", "pixel format RGB_888");
    946            // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
    947            sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888
    948            break;
    949        default:
    950            Log.v("SDL", "pixel format unknown " + format);
    951            break;
    952        }
    953
    954        mWidth = width;
    955        mHeight = height;
    956        SDLActivity.onNativeResize(width, height, sdlFormat);
    957        Log.v("SDL", "Window size:" + width + "x"+height);
    958
    959        // Set mIsSurfaceReady to 'true' *before* making a call to handleResume
    960        SDLActivity.mIsSurfaceReady = true;
    961        SDLActivity.onNativeSurfaceChanged();
    962
    963
    964        if (SDLActivity.mSDLThread == null) {
    965            // This is the entry point to the C app.
    966            // Start up the C app thread and enable sensor input for the first time
    967
    968            final Thread sdlThread = new Thread(new SDLMain(), "SDLThread");
    969            enableSensor(Sensor.TYPE_ACCELEROMETER, true);
    970            sdlThread.start();
    971            
    972            // Set up a listener thread to catch when the native thread ends
    973            SDLActivity.mSDLThread = new Thread(new Runnable(){
    974                @Override
    975                public void run(){
    976                    try {
    977                        sdlThread.join();
    978                    }
    979                    catch(Exception e){}
    980                    finally{ 
    981                        // Native thread has finished
    982                        if (! SDLActivity.mExitCalledFromJava) {
    983                            SDLActivity.handleNativeExit();
    984                        }
    985                    }
    986                }
    987            });
    988            SDLActivity.mSDLThread.start();
    989        }
    990    }
    991
    992    // unused
    993    @Override
    994    public void onDraw(Canvas canvas) {}
    995
    996
    997    // Key events
    998    @Override
    999    public boolean onKey(View  v, int keyCode, KeyEvent event) {
   1000        // Dispatch the different events depending on where they come from
   1001        // Some SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
   1002        // So, we try to process them as DPAD or GAMEPAD events first, if that fails we try them as KEYBOARD
   1003        
   1004        if ( (event.getSource() & 0x00000401) != 0 || /* API 12: SOURCE_GAMEPAD */
   1005                   (event.getSource() & InputDevice.SOURCE_DPAD) != 0 ) {
   1006            if (event.getAction() == KeyEvent.ACTION_DOWN) {
   1007                if (SDLActivity.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
   1008                    return true;
   1009                }
   1010            } else if (event.getAction() == KeyEvent.ACTION_UP) {
   1011                if (SDLActivity.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
   1012                    return true;
   1013                }
   1014            }
   1015        }
   1016        
   1017        if( (event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
   1018            if (event.getAction() == KeyEvent.ACTION_DOWN) {
   1019                //Log.v("SDL", "key down: " + keyCode);
   1020                SDLActivity.onNativeKeyDown(keyCode);
   1021                return true;
   1022            }
   1023            else if (event.getAction() == KeyEvent.ACTION_UP) {
   1024                //Log.v("SDL", "key up: " + keyCode);
   1025                SDLActivity.onNativeKeyUp(keyCode);
   1026                return true;
   1027            }
   1028        }
   1029        
   1030        return false;
   1031    }
   1032
   1033    // Touch events
   1034    @Override
   1035    public boolean onTouch(View v, MotionEvent event) {
   1036        /* Ref: http://developer.android.com/training/gestures/multi.html */
   1037        final int touchDevId = event.getDeviceId();
   1038        final int pointerCount = event.getPointerCount();
   1039        int action = event.getActionMasked();
   1040        int pointerFingerId;
   1041        int i = -1;
   1042        float x,y,p;
   1043        
   1044        switch(action) {
   1045            case MotionEvent.ACTION_MOVE:
   1046                for (i = 0; i < pointerCount; i++) {
   1047                    pointerFingerId = event.getPointerId(i);
   1048                    x = event.getX(i) / mWidth;
   1049                    y = event.getY(i) / mHeight;
   1050                    p = event.getPressure(i);
   1051                    SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
   1052                }
   1053                break;
   1054            
   1055            case MotionEvent.ACTION_UP:
   1056            case MotionEvent.ACTION_DOWN:
   1057                // Primary pointer up/down, the index is always zero
   1058                i = 0;
   1059            case MotionEvent.ACTION_POINTER_UP:
   1060            case MotionEvent.ACTION_POINTER_DOWN:
   1061                // Non primary pointer up/down
   1062                if (i == -1) {
   1063                    i = event.getActionIndex();
   1064                }
   1065                
   1066                pointerFingerId = event.getPointerId(i);
   1067                x = event.getX(i) / mWidth;
   1068                y = event.getY(i) / mHeight;
   1069                p = event.getPressure(i);
   1070                SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
   1071                break;
   1072            
   1073            case MotionEvent.ACTION_CANCEL:
   1074                for (i = 0; i < pointerCount; i++) {
   1075                    pointerFingerId = event.getPointerId(i);
   1076                    x = event.getX(i) / mWidth;
   1077                    y = event.getY(i) / mHeight;
   1078                    p = event.getPressure(i);
   1079                    SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
   1080                }
   1081                break;
   1082
   1083            default:
   1084                break;
   1085        }
   1086
   1087        return true;
   1088   } 
   1089
   1090    // Sensor events
   1091    public void enableSensor(int sensortype, boolean enabled) {
   1092        // TODO: This uses getDefaultSensor - what if we have >1 accels?
   1093        if (enabled) {
   1094            mSensorManager.registerListener(this, 
   1095                            mSensorManager.getDefaultSensor(sensortype), 
   1096                            SensorManager.SENSOR_DELAY_GAME, null);
   1097        } else {
   1098            mSensorManager.unregisterListener(this, 
   1099                            mSensorManager.getDefaultSensor(sensortype));
   1100        }
   1101    }
   1102    
   1103    @Override
   1104    public void onAccuracyChanged(Sensor sensor, int accuracy) {
   1105        // TODO
   1106    }
   1107
   1108    @Override
   1109    public void onSensorChanged(SensorEvent event) {
   1110        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
   1111            float x, y;
   1112            switch (mDisplay.getRotation()) {
   1113                case Surface.ROTATION_90:
   1114                    x = -event.values[1];
   1115                    y = event.values[0];
   1116                    break;
   1117                case Surface.ROTATION_270:
   1118                    x = event.values[1];
   1119                    y = -event.values[0];
   1120                    break;
   1121                case Surface.ROTATION_180:
   1122                    x = -event.values[1];
   1123                    y = -event.values[0];
   1124                    break;
   1125                default:
   1126                    x = event.values[0];
   1127                    y = event.values[1];
   1128                    break;
   1129            }
   1130            SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
   1131                                      y / SensorManager.GRAVITY_EARTH,
   1132                                      event.values[2] / SensorManager.GRAVITY_EARTH - 1);
   1133        }
   1134    }    
   1135}
   1136
   1137/* This is a fake invisible editor view that receives the input and defines the
   1138 * pan&scan region
   1139 */
   1140class DummyEdit extends View implements View.OnKeyListener {
   1141    InputConnection ic;
   1142
   1143    public DummyEdit(Context context) {
   1144        super(context);
   1145        setFocusableInTouchMode(true);
   1146        setFocusable(true);
   1147        setOnKeyListener(this);
   1148    }
   1149
   1150    @Override
   1151    public boolean onCheckIsTextEditor() {
   1152        return true;
   1153    }
   1154
   1155    @Override
   1156    public boolean onKey(View v, int keyCode, KeyEvent event) {
   1157
   1158        // This handles the hardware keyboard input
   1159        if (event.isPrintingKey()) {
   1160            if (event.getAction() == KeyEvent.ACTION_DOWN) {
   1161                ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
   1162            }
   1163            return true;
   1164        }
   1165
   1166        if (event.getAction() == KeyEvent.ACTION_DOWN) {
   1167            SDLActivity.onNativeKeyDown(keyCode);
   1168            return true;
   1169        } else if (event.getAction() == KeyEvent.ACTION_UP) {
   1170            SDLActivity.onNativeKeyUp(keyCode);
   1171            return true;
   1172        }
   1173
   1174        return false;
   1175    }
   1176        
   1177    //
   1178    @Override
   1179    public boolean onKeyPreIme (int keyCode, KeyEvent event) {
   1180        // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
   1181        // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
   1182        // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
   1183        // FIXME: A more effective solution would be to change our Layout from AbsoluteLayout to Relative or Linear
   1184        // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
   1185        // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
   1186        if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
   1187            if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
   1188                SDLActivity.onNativeKeyboardFocusLost();
   1189            }
   1190        }
   1191        return super.onKeyPreIme(keyCode, event);
   1192    }
   1193
   1194    @Override
   1195    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
   1196        ic = new SDLInputConnection(this, true);
   1197
   1198        outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
   1199                | 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */;
   1200
   1201        return ic;
   1202    }
   1203}
   1204
   1205class SDLInputConnection extends BaseInputConnection {
   1206
   1207    public SDLInputConnection(View targetView, boolean fullEditor) {
   1208        super(targetView, fullEditor);
   1209
   1210    }
   1211
   1212    @Override
   1213    public boolean sendKeyEvent(KeyEvent event) {
   1214
   1215        /*
   1216         * This handles the keycodes from soft keyboard (and IME-translated
   1217         * input from hardkeyboard)
   1218         */
   1219        int keyCode = event.getKeyCode();
   1220        if (event.getAction() == KeyEvent.ACTION_DOWN) {
   1221            if (event.isPrintingKey()) {
   1222                commitText(String.valueOf((char) event.getUnicodeChar()), 1);
   1223            }
   1224            SDLActivity.onNativeKeyDown(keyCode);
   1225            return true;
   1226        } else if (event.getAction() == KeyEvent.ACTION_UP) {
   1227
   1228            SDLActivity.onNativeKeyUp(keyCode);
   1229            return true;
   1230        }
   1231        return super.sendKeyEvent(event);
   1232    }
   1233
   1234    @Override
   1235    public boolean commitText(CharSequence text, int newCursorPosition) {
   1236
   1237        nativeCommitText(text.toString(), newCursorPosition);
   1238
   1239        return super.commitText(text, newCursorPosition);
   1240    }
   1241
   1242    @Override
   1243    public boolean setComposingText(CharSequence text, int newCursorPosition) {
   1244
   1245        nativeSetComposingText(text.toString(), newCursorPosition);
   1246
   1247        return super.setComposingText(text, newCursorPosition);
   1248    }
   1249
   1250    public native void nativeCommitText(String text, int newCursorPosition);
   1251
   1252    public native void nativeSetComposingText(String text, int newCursorPosition);
   1253
   1254    @Override
   1255    public boolean deleteSurroundingText(int beforeLength, int afterLength) {       
   1256        // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
   1257        if (beforeLength == 1 && afterLength == 0) {
   1258            // backspace
   1259            return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
   1260                && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
   1261        }
   1262
   1263        return super.deleteSurroundingText(beforeLength, afterLength);
   1264    }
   1265}
   1266
   1267/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */
   1268class SDLJoystickHandler {
   1269    
   1270    /**
   1271     * Handles given MotionEvent.
   1272     * @param event the event to be handled.
   1273     * @return if given event was processed.
   1274     */
   1275    public boolean handleMotionEvent(MotionEvent event) {
   1276        return false;
   1277    }
   1278
   1279    /**
   1280     * Handles adding and removing of input devices.
   1281     */
   1282    public void pollInputDevices() {
   1283    }
   1284}
   1285
   1286/* Actual joystick functionality available for API >= 12 devices */
   1287class SDLJoystickHandler_API12 extends SDLJoystickHandler {
   1288
   1289    static class SDLJoystick {
   1290        public int device_id;
   1291        public String name;
   1292        public ArrayList<InputDevice.MotionRange> axes;
   1293        public ArrayList<InputDevice.MotionRange> hats;
   1294    }
   1295    static class RangeComparator implements Comparator<InputDevice.MotionRange> {
   1296        @Override
   1297        public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
   1298            return arg0.getAxis() - arg1.getAxis();
   1299        }
   1300    }
   1301    
   1302    private ArrayList<SDLJoystick> mJoysticks;
   1303    
   1304    public SDLJoystickHandler_API12() {
   1305       
   1306        mJoysticks = new ArrayList<SDLJoystick>();
   1307    }
   1308
   1309    @Override
   1310    public void pollInputDevices() {
   1311        int[] deviceIds = InputDevice.getDeviceIds();
   1312        // It helps processing the device ids in reverse order
   1313        // For example, in the case of the XBox 360 wireless dongle,
   1314        // so the first controller seen by SDL matches what the receiver
   1315        // considers to be the first controller
   1316        
   1317        for(int i=deviceIds.length-1; i>-1; i--) {
   1318            SDLJoystick joystick = getJoystick(deviceIds[i]);
   1319            if (joystick == null) {
   1320                joystick = new SDLJoystick();
   1321                InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
   1322                if( (joystickDevice.getSources() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
   1323                    joystick.device_id = deviceIds[i];
   1324                    joystick.name = joystickDevice.getName();
   1325                    joystick.axes = new ArrayList<InputDevice.MotionRange>();
   1326                    joystick.hats = new ArrayList<InputDevice.MotionRange>();
   1327                    
   1328                    List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
   1329                    Collections.sort(ranges, new RangeComparator());
   1330                    for (InputDevice.MotionRange range : ranges ) {
   1331                        if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) {
   1332                            if (range.getAxis() == MotionEvent.AXIS_HAT_X ||
   1333                                range.getAxis() == MotionEvent.AXIS_HAT_Y) {
   1334                                joystick.hats.add(range);
   1335                            }
   1336                            else {
   1337                                joystick.axes.add(range);
   1338                            }
   1339                        }
   1340                    }
   1341                    
   1342                    mJoysticks.add(joystick);
   1343                    SDLActivity.nativeAddJoystick(joystick.device_id, joystick.name, 0, -1, 
   1344                                                  joystick.axes.size(), joystick.hats.size()/2, 0);
   1345                }
   1346            }
   1347        }
   1348        
   1349        /* Check removed devices */
   1350        ArrayList<Integer> removedDevices = new ArrayList<Integer>();
   1351        for(int i=0; i < mJoysticks.size(); i++) {
   1352            int device_id = mJoysticks.get(i).device_id;
   1353            int j;
   1354            for (j=0; j < deviceIds.length; j++) {
   1355                if (device_id == deviceIds[j]) break;
   1356            }
   1357            if (j == deviceIds.length) {
   1358                removedDevices.add(Integer.valueOf(device_id));
   1359            }
   1360        }
   1361            
   1362        for(int i=0; i < removedDevices.size(); i++) {
   1363            int device_id = removedDevices.get(i).intValue();
   1364            SDLActivity.nativeRemoveJoystick(device_id);
   1365            for (int j=0; j < mJoysticks.size(); j++) {
   1366                if (mJoysticks.get(j).device_id == device_id) {
   1367                    mJoysticks.remove(j);
   1368                    break;
   1369                }
   1370            }
   1371        }        
   1372    }
   1373    
   1374    protected SDLJoystick getJoystick(int device_id) {
   1375        for(int i=0; i < mJoysticks.size(); i++) {
   1376            if (mJoysticks.get(i).device_id == device_id) {
   1377                return mJoysticks.get(i);
   1378            }
   1379        }
   1380        return null;
   1381    }   
   1382    
   1383    @Override        
   1384    public boolean handleMotionEvent(MotionEvent event) {
   1385        if ( (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
   1386            int actionPointerIndex = event.getActionIndex();
   1387            int action = event.getActionMasked();
   1388            switch(action) {
   1389                case MotionEvent.ACTION_MOVE:
   1390                    SDLJoystick joystick = getJoystick(event.getDeviceId());
   1391                    if ( joystick != null ) {
   1392                        for (int i = 0; i < joystick.axes.size(); i++) {
   1393                            InputDevice.MotionRange range = joystick.axes.get(i);
   1394                            /* Normalize the value to -1...1 */
   1395                            float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
   1396                            SDLActivity.onNativeJoy(joystick.device_id, i, value );
   1397                        }          
   1398                        for (int i = 0; i < joystick.hats.size(); i+=2) {
   1399                            int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) );
   1400                            int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) );
   1401                            SDLActivity.onNativeHat(joystick.device_id, i/2, hatX, hatY );
   1402                        }
   1403                    }
   1404                    break;
   1405                default:
   1406                    break;
   1407            }
   1408        }
   1409        return true;
   1410    }            
   1411}
   1412
   1413class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
   1414    // Generic Motion (mouse hover, joystick...) events go here
   1415    // We only have joysticks yet
   1416    @Override
   1417    public boolean onGenericMotion(View v, MotionEvent event) {
   1418        return SDLActivity.handleJoystickMotionEvent(event);
   1419    }
   1420}