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