uk.co.armedpineapple.cth.SDLActivity.java Source code

Java tutorial

Introduction

Here is the source code for uk.co.armedpineapple.cth.SDLActivity.java

Source

/*
 *   Copyright (C) 2012 Alan Woolley
 *   
 *   See LICENSE.TXT for full license
 */
// $codepro.audit.disable disallowNativeMethods
package uk.co.armedpineapple.cth;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager.NameNotFoundException;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.DrawerLayout.DrawerListener;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ListView;

import com.bugsense.trace.BugSenseHandler;
import com.immersion.uhl.Launcher;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

import javax.microedition.khronos.egl.EGL10;
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 uk.co.armedpineapple.cth.CommandHandler.Command;
import uk.co.armedpineapple.cth.Files.FileDetails;
import uk.co.armedpineapple.cth.Files.StorageUnavailableException;
import uk.co.armedpineapple.cth.Files.UnzipTask;
import uk.co.armedpineapple.cth.dialogs.DialogFactory;

public class SDLActivity extends CTHActivity {

    public static final String LOG_TAG = "SDLActivity";
    private static final String ENGINE_ZIP_FILE = "game.zip";
    // Main components
    public static SDLActivity mSingleton;
    public static SDLSurface mSurface;
    // This is what SDL runs in. It invokes SDL_main(), eventually
    private static Thread mSDLThread;
    // EGL private objects
    private static EGLContext mEGLContext;
    private static EGLSurface mEGLSurface;
    private static EGLDisplay mEGLDisplay;
    private static EGLConfig mEGLConfig;
    private static int mGLMajor, mGLMinor;
    // Audio
    private static Thread mAudioThread;
    private static AudioTrack mAudioTrack;
    private static Object audioBuffer;
    // Handler for the messages
    public CommandHandler commandHandler;
    // Menu Drawer
    DrawerLayout mDrawerLayout;
    private ListView mDrawerList;
    private int currentVersion;
    private WakeLock wake;
    private boolean hasGameLoaded = false;
    // Vibration launcher
    private Launcher mHapticLauncher;

    // C functions we call
    public static native void nativeInit(Configuration config, String toLoad);

    public static native void nativeQuit();

    public static native void onNativeResize(int x, int y, int format);

    public static native void onNativeKeyDown(int keycode);

    public static native void onNativeKeyUp(int keycode);

    public static native void onNativeTouch(int touchDevId, int pointerFingerId, int action, float x, float y,
            float p, int pc, int gestureTriggered, int controlsMode);

    public static native void onNativeAccel(float x, float y, float z);

    public static native void onNativeHover(float x, float y);

    public static native void onSpenButton();

    public static native void onNativeLowMemory();

    public static native void nativeRunAudioThread();

    public static native void cthRestartGame();

    public static native void cthSaveGame(String path);

    public static native void cthLoadGame(String path);

    public static native void cthGameSpeed(int speed);

    public static native void cthTryAutoSave(String filename);

    public static native void cthUpdateConfiguration(Configuration config);

    public static native void cthShowCheats();

    public static String nativeGetGamePath() {
        return mSingleton.app.configuration.getCthPath() + "/scripts/";
    }

    // EGL functions
    public static boolean initEGL(int majorVersion, int minorVersion) {
        if (SDLActivity.mEGLDisplay == null) {
            Log.v(SDLActivity.class.getSimpleName(), "Starting up OpenGL ES " + majorVersion + "." + minorVersion);

            try {
                EGL10 egl = (EGL10) EGLContext.getEGL();

                EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);

                int[] version = new int[2];
                egl.eglInitialize(dpy, version);

                int EGL_OPENGL_ES_BIT = 1;
                int EGL_OPENGL_ES2_BIT = 4;
                int renderableType = 0;
                if (majorVersion == 2) {
                    renderableType = EGL_OPENGL_ES2_BIT;
                } else if (majorVersion == 1) {
                    renderableType = EGL_OPENGL_ES_BIT;
                }

                int[] configSpec = {
                        // EGL10.EGL_DEPTH_SIZE, 16,
                        EGL10.EGL_RENDERABLE_TYPE, renderableType, EGL10.EGL_NONE };
                EGLConfig[] configs = new EGLConfig[1];
                int[] num_config = new int[1];
                if (!egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config) || num_config[0] == 0) {
                    Log.e(SDLActivity.class.getSimpleName(), "No EGL config available");
                    return false;
                }
                EGLConfig config = configs[0];

                SDLActivity.mEGLDisplay = dpy;
                SDLActivity.mEGLConfig = config;
                SDLActivity.mGLMajor = majorVersion;
                SDLActivity.mGLMinor = minorVersion;

                SDLActivity.createEGLSurface();
            } catch (Exception e) {
                Log.v("SDL", e + "");
                for (StackTraceElement s : e.getStackTrace()) {
                    Log.v("SDL", s.toString());
                }
            }
        } else
            SDLActivity.createEGLSurface();

        return true;
    }

    public static boolean createEGLContext() {
        EGL10 egl = (EGL10) EGLContext.getEGL();
        int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
        int contextAttrs[] = new int[] { EGL_CONTEXT_CLIENT_VERSION, SDLActivity.mGLMajor, EGL10.EGL_NONE };
        SDLActivity.mEGLContext = egl.eglCreateContext(SDLActivity.mEGLDisplay, SDLActivity.mEGLConfig,
                EGL10.EGL_NO_CONTEXT, contextAttrs);
        if (SDLActivity.mEGLContext == EGL10.EGL_NO_CONTEXT) {
            Log.e("SDL", "Couldn't create activityContext");
            return false;
        }
        return true;
    }

    public static boolean createEGLSurface() {
        if (SDLActivity.mEGLDisplay != null && SDLActivity.mEGLConfig != null) {
            EGL10 egl = (EGL10) EGLContext.getEGL();
            if (SDLActivity.mEGLContext == null)
                createEGLContext();

            Log.v("SDL", "Creating new EGL Surface");
            EGLSurface surface = egl.eglCreateWindowSurface(SDLActivity.mEGLDisplay, SDLActivity.mEGLConfig,
                    SDLActivity.mSurface, null);
            if (surface == EGL10.EGL_NO_SURFACE) {
                Log.e("SDL", "Couldn't create surface");
                return false;
            }

            if (!egl.eglMakeCurrent(SDLActivity.mEGLDisplay, surface, surface, SDLActivity.mEGLContext)) {
                Log.e("SDL", "Old EGL Context doesnt work, trying with a new one");
                createEGLContext();
                if (!egl.eglMakeCurrent(SDLActivity.mEGLDisplay, surface, surface, SDLActivity.mEGLContext)) {
                    Log.e("SDL", "Failed making EGL Context current");
                    return false;
                }
            }
            SDLActivity.mEGLSurface = surface;
            return true;
        }
        return false;
    }

    public static boolean createGLContext(int majorVersion, int minorVersion) {
        return initEGL(majorVersion, minorVersion);
    }

    public static void flipBuffers() {
        flipEGL();
    }

    // EGL buffer flip
    public static void flipEGL() {
        try {
            EGL10 egl = (EGL10) EGLContext.getEGL();

            egl.eglWaitNative(EGL10.EGL_CORE_NATIVE_ENGINE, null);

            // drawing here

            egl.eglWaitGL();

            egl.eglSwapBuffers(SDLActivity.mEGLDisplay, SDLActivity.mEGLSurface);

        } catch (Exception e) {
            Log.v("SDL", "flipEGL(): " + e);
            for (StackTraceElement s : e.getStackTrace()) {
                Log.v("SDL", s.toString());
            }
        }
    }

    public static void setActivityTitle(String title) {
    }

    public static void sendCommand(Command command, Object data) {
        sendCommand(command.ordinal(), data);
    }

    public static void sendCommand(int cmd, int data) {
        sendCommand(cmd, Integer.valueOf(data));
    }

    public static void sendCommand(int cmd) {
        sendCommand(cmd, null);
    }

    public static void sendCommand(int cmd, Object data) {
        Message msg = mSingleton.commandHandler.obtainMessage();
        msg.arg1 = cmd;
        msg.obj = data;
        mSingleton.commandHandler.sendMessage(msg);
    }

    public static Object audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
        int channelConfig = isStereo ? AudioFormat.CHANNEL_OUT_STEREO : AudioFormat.CHANNEL_OUT_MONO;
        int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
        int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);

        Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit")
                + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");

        // Let the user pick a larger buffer if they really want -- but ye
        // gods they probably shouldn't, the minimums are horrifyingly high
        // latency already
        desiredFrames = Math.max(desiredFrames,
                (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);

        mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat,
                desiredFrames * frameSize, AudioTrack.MODE_STREAM);

        audioStartThread();

        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");

        if (is16Bit) {
            audioBuffer = new short[desiredFrames * (isStereo ? 2 : 1)];
        } else {
            audioBuffer = new byte[desiredFrames * (isStereo ? 2 : 1)];
        }
        return audioBuffer;
    }

    public static void audioStartThread() {
        mAudioThread = new Thread(new Runnable() {
            public void run() {
                mAudioTrack.play();
                nativeRunAudioThread();
            }
        }, "Audio Thread");

        // I'd take REALTIME if I could get it!
        mAudioThread.setPriority(Thread.MAX_PRIORITY);
        mAudioThread.start();
    }

    public static void audioWriteShortBuffer(short[] buffer) {
        for (int i = 0; i < buffer.length;) {
            int result = mAudioTrack.write(buffer, i, buffer.length - i);
            if (result > 0) {
                i += result;
            } else if (result == 0) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    // Nom nom
                }
            } else {
                Log.w(SDLActivity.class.getSimpleName(), "SDL audio: error return from write(short)");
                return;
            }
        }
    }

    // Java functions called from C

    public static void audioWriteByteBuffer(byte[] buffer) {
        for (int i = 0; i < buffer.length;) {
            int result = mAudioTrack.write(buffer, i, buffer.length - i);
            if (result > 0) {
                i += result;
            } else if (result == 0) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    // Nom nom
                }
            } else {
                Log.w(SDLActivity.class.getSimpleName(), "SDL audio: error return from write(byte)");
                return;
            }
        }
    }

    // Send a message from the SDLMain thread

    public static void audioQuit() {
        if (mAudioThread != null) {
            try {
                mAudioThread.join();
            } catch (Exception e) {
                Log.v(SDLActivity.class.getSimpleName(), "Problem stopping audio thread: " + e);
                BugSenseHandler.sendException(e);
            }
            mAudioThread = null;

        }

        if (mAudioTrack != null) {
            mAudioTrack.stop();
            mAudioTrack = null;
        }
    }

    public static void toggleScrolling(boolean scrolling) {
        Log.d(SDLActivity.class.getSimpleName(), "Scrolling Java call: " + scrolling);
        mSurface.setScrolling(scrolling);
    }

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        commandHandler = new CommandHandler(this);
        // The volume buttons should change the media volume
        setVolumeControlStream(AudioManager.STREAM_MUSIC);

        // Make sure that external media is mounted.
        if (Files.canAccessExternalStorage()) {

            final SharedPreferences preferences = app.getPreferences();

            if (app.configuration == null) {
                try {
                    app.configuration = Configuration.loadFromPreferences(this, preferences);
                } catch (StorageUnavailableException e) {
                    Log.e(LOG_TAG, "Can't get storage.");

                    // Create an alert dialog warning that external storage isn't
                    // mounted. The application will have to exit at this point.

                    DialogFactory.createExternalStorageWarningDialog(this, true).show();
                }
            }

            currentVersion = preferences.getInt("last_version", 0) - 1;

            try {
                currentVersion = (getPackageManager().getPackageInfo(getPackageName(), 0).versionCode);

            } catch (NameNotFoundException e) {
                BugSenseHandler.sendException(e);
            }

            // Check to see if the game files have been copied yet, or whether the
            // application has been updated
            if (!preferences.getBoolean("scripts_copied", false)
                    || preferences.getInt("last_version", 0) < currentVersion) {

                Log.d(LOG_TAG, "This is a new installation");

                // Show the recent changes dialog
                Dialog recentChangesDialog = DialogFactory.createRecentChangesDialog(this);
                recentChangesDialog.setOnDismissListener(new OnDismissListener() {

                    @Override
                    public void onDismiss(DialogInterface arg0) {
                        installFiles(preferences);
                    }

                });
                recentChangesDialog.show();

            } else {

                // Continue to load the application otherwise
                loadApplication();
            }

        } else {
            Log.e(LOG_TAG, "Can't get storage.");

            // Create an alert dialog warning that external storage isn't
            // mounted. The application will have to exit at this point.

            DialogFactory.createExternalStorageWarningDialog(this, true).show();
        }
    }

    private void installFiles(final SharedPreferences preferences) {
        final ProgressDialog dialog = new ProgressDialog(this);
        final UnzipTask unzipTask = new UnzipTask(app.configuration.getCthPath() + "/scripts/", this) {

            @Override
            protected void onPreExecute() {
                super.onPreExecute();
                dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                dialog.setMessage(getString(R.string.preparing_game_files_dialog));
                dialog.setIndeterminate(false);
                dialog.setCancelable(false);
                dialog.show();
            }

            @Override
            protected void onProgressUpdate(Integer... values) {
                super.onProgressUpdate(values);
                dialog.setProgress(values[0]);
                dialog.setMax(values[1]);
            }

            @Override
            protected void onPostExecute(AsyncTaskResult<String> result) {
                super.onPostExecute(result);
                Exception error;
                if ((error = result.getError()) != null) {
                    Log.d(LOG_TAG, "Error copying files.");
                    BugSenseHandler.sendException(error);
                }

                Editor edit = preferences.edit();
                edit.putBoolean("scripts_copied", true);
                edit.putInt("last_version", currentVersion);
                edit.commit();
                dialog.hide();
                loadApplication();
            }

        };

        AsyncTask<String, Void, AsyncTaskResult<File>> copyTask = new

        AsyncTask<String, Void, AsyncTaskResult<File>>() {

            @Override
            protected AsyncTaskResult<File> doInBackground(String... params) {

                try {
                    Files.copyAsset(SDLActivity.this, params[0], params[1]);
                } catch (IOException e) {

                    return new AsyncTaskResult<File>(e);
                }
                return new AsyncTaskResult<File>(new File(params[1] + "/" + params[0]));
            }

            @Override
            protected void onPostExecute(AsyncTaskResult<File> result) {
                super.onPostExecute(result);
                File f;
                if ((f = result.getResult()) != null) {
                    unzipTask.execute(f);
                } else {
                    BugSenseHandler.sendException(result.getError());

                }
            }

        };

        if (Files.canAccessExternalStorage()) {

            copyTask.execute(ENGINE_ZIP_FILE, getExternalCacheDir().getAbsolutePath());
        } else {
            DialogFactory.createExternalStorageWarningDialog(this, true).show();
        }
    }

    void loadApplication() {

        // Load the libraries
        System.loadLibrary("SDL");
        System.loadLibrary("luajit");
        System.loadLibrary("SDL_mixer");
        System.loadLibrary("ffmpeg");
        System.loadLibrary("appmain");

        try {
            app.configuration.writeToFile();
        } catch (IOException e) {
            e.printStackTrace();
            Log.e(LOG_TAG, "Couldn't write to configuration file");
            BugSenseHandler.sendException(e);
        }

        File f = new File(app.configuration.getSaveGamesPath());

        if (!f.isDirectory()) {
            f.mkdirs();
        }

        // So we can call stuff from static callbacks
        mSingleton = this;

        hideSystemUi();

        mSurface = new SDLSurface(this, app.configuration.getDisplayWidth(), app.configuration.getDisplayHeight());
        mSurface.setZOrderOnTop(false);

        DrawerLayout mainLayout = (DrawerLayout) getLayoutInflater().inflate(R.layout.game, null);
        FrameLayout gameFrame = ((FrameLayout) mainLayout.findViewById(R.id.game_frame));

        gameFrame.addView(mSurface);
        setContentView(mainLayout);

        mHapticLauncher = new Launcher(this);

        mDrawerLayout = (DrawerLayout) findViewById(R.id.main_layout);
        mDrawerList = (ListView) findViewById(R.id.menu_drawer);
        mDrawerList.setAdapter(new NavDrawerAdapter(this,
                uk.co.armedpineapple.cth.MenuItems.getItems(BuildConfig.DEBUG || app.configuration.getDebug())));
        mDrawerList.setOnItemClickListener(new NavDrawerListListener(this));
        mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
        mDrawerLayout.setDrawerListener(new DrawerListener() {

            @Override
            public void onDrawerClosed(View arg0) {
                // Restore game speed
                cthGameSpeed(app.configuration.getGameSpeed());
                mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
            }

            @Override
            public void onDrawerOpened(View arg0) {
                // Pause the game
                cthGameSpeed(0);
            }

            @Override
            public void onDrawerSlide(View arg0, float arg1) {
                arg0.bringToFront();
                mDrawerLayout.bringChildToFront(arg0);
                mDrawerLayout.requestLayout();

            }

            @Override
            public void onDrawerStateChanged(int arg0) {
                // TODO Auto-generated method stub

            }

        });
        SurfaceHolder holder = mSurface.getHolder();
        holder.setFixedSize(app.configuration.getDisplayWidth(), app.configuration.getDisplayHeight());

        gameFrame.setVisibility(View.VISIBLE);

        hasGameLoaded = true;

    }

    @SuppressLint("NewApi")
    public void hideSystemUi() {
        if (Build.VERSION.SDK_INT >= 19) {

            // Hide the navigation buttons if supported
            getWindow().getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN);
        } else if (Build.VERSION.SDK_INT >= 11) {

            // Use low profile mode if supported
            getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);

        }
    }

    public void startApp() {
        // Start up the C app thread

        if (mSDLThread == null) {

            List<FileDetails> saves = null;
            try {
                saves = Files.listFilesInDirectory(app.configuration.getSaveGamesPath(), new FilenameFilter() {

                    @Override
                    public boolean accept(File dir, String filename) {
                        return filename.toLowerCase(Locale.US).endsWith(".sav");
                    }
                });
            } catch (IOException e) {
            }

            if (saves != null && saves.size() > 0) {
                Collections.sort(saves, Collections.reverseOrder());

                final String loadPath = saves.get(0).getFileName();

                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setMessage(R.string.load_last_save);
                builder.setCancelable(false);
                builder.setPositiveButton(R.string.yes, new Dialog.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                        mSDLThread = new Thread(new SDLMain(app.configuration, loadPath), "SDLThread");
                        mSDLThread.start();
                    }

                });
                builder.setNegativeButton(R.string.no, new Dialog.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        mSDLThread = new Thread(new SDLMain(app.configuration, ""), "SDLThread");
                        mSDLThread.start();
                    }

                });
                builder.create().show();

            } else {

                mSDLThread = new Thread(new SDLMain(app.configuration, ""), "SDLThread");
                mSDLThread.start();
            }

        }
    }

    // Events
    protected void onPause() {
        super.onPause();
        Log.d(LOG_TAG, "onPause()");

        // Attempt to autosave.
        if (hasGameLoaded) {
            // Reset the game speed back to normal
            cthGameSpeed(app.configuration.getGameSpeed());

            cthTryAutoSave("cthAndroidAutoSave.sav");
        }

        if (wake != null && wake.isHeld()) {
            Log.d(LOG_TAG, "Releasing wakelock");
            wake.release();
        }

        if (mHapticLauncher != null) {
            mHapticLauncher.stop();
        }

    }

    protected void onResume() {
        super.onResume();
        Log.d(LOG_TAG, "onResume()");

        if (app.configuration != null && app.configuration.getKeepScreenOn()) {
            Log.d(LOG_TAG, "Getting wakelock");
            PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
            wake = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "Keep Screen On Wakelock");
            wake.acquire();
        }

        if (mDrawerLayout != null) {
            mDrawerLayout.closeDrawers();
        }

    }

    private void restartActivity() {
        Intent intent = getIntent();
        finish();
        startActivity(intent);
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        Log.w(LOG_TAG, "Low memory detected. Going to try and tighten our belt!");

        if (hasGameLoaded) {
            // Attempt to save first
            cthTryAutoSave(getString(R.string.autosave_name));
        }

        // Remove references to some stuff that can just be regenerated later, so
        // that the GC can get rid of them.
        commandHandler.cleanUp();

        // Delete the reference to the haptic launcher - we can recreate it later if needed
        stopVibration();
        mHapticLauncher = null;

        // Call LUA GC
        // TODO - this is buggy.
        // onNativeLowMemory();

    }

    public void playVibration(int vibrationCode) {
        if (mHapticLauncher == null)
            mHapticLauncher = new Launcher(this);
        if (app.hasVibration) {
            mHapticLauncher.play(vibrationCode);
        }
    }

    public void stopVibration() {
        if (mHapticLauncher != null && app.hasVibration) {
            mHapticLauncher.stop();
            commandHandler.playingEarthquake = false;
        }

    }
}

/**
 * Simple nativeInit() runnable
 */
class SDLMain implements Runnable {
    private static final String LOG_TAG = "SDLMain";
    private final Configuration config;
    private final String toLoad;

    public SDLMain(Configuration config, String toLoad) {
        this.config = config;
        this.toLoad = toLoad;

    }

    public void run() {
        // Runs SDL_main()
        SDLActivity.nativeInit(config, toLoad);

        Log.v(LOG_TAG, "SDL thread terminated");
    }
}