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