Xbox 360 controller not recognized on Android 4.0

I personally would be very happy if this get’s into the core, even if it’s only with Android 3.1+ support.

I think you should consider to support this only with Android 3.1+.

@iwgeric: For 2.2 devices, its possible to avoid loading the gamepad specific JoyInput class.

@Pixelapp: does the TestJoystick sample application work with your code? Ideally, it should work exactly the same on desktop as it does on Android with the same gamepad.

@Momoko_Fan Not yet. @ iwgeric instructions to me were to replace the sensor for the android gamepad. So for now we have to activate the android sensor in order to use the android gamepad.

I know what you mean. But that will just have to wait.

1 Like

I’m fulfilling @Momoko_Fan 's request to make the android gamepad work with the TestJoystick.java sample right now.

1 Like

@Pixelapp : Thanks for this.

Courtesy structural change notice:

Since android 3.1 doesn’t have a Gamepad manager, we won’t be able to query the joystick in the simpeinit() method on android. Since I’ll be doing this on android I’ll do this on pc as well, in my Jmonkey source anyways. So, from now on we won’t be able to query the gamepad on the simpleinit() method.

In short, no simpleinit() method for polling the joystick in either PC or Android. If someone wants to add simpleinit() method gamepad polling, he/she will have to do it after I submit my commit. Or will have to explain to me how to keep the PC side simpleInit() gamepad polling, without having it on android.

Why can it be done on update but not on init? These methods are both called from Application.update()

Edit: or are you saying there is no polling support at all… just events?

When you interact with the gamepad on android 3.1 , you only get the gamepadId after the user interacts with the gampad through the ongenericmotion(… …) method. You can not ask android about gamepads before the user interacts with the gamepad. This problem is fixed at around android api 4.1 but still we have to use android api 3.1 and other android api levels on Jmonkey so the problem persists for us.

This is what provides us with the game pad information after and only after the user interacts with the gamepad.

@Pixelapp said: When you interact with the gamepad on android 3.1 , you only get the gamepadId after the user interacts with the gampad through the ongenericmotion(... ...) method. You can not ask android about gamepads before the user interacts with the gamepad. This problem is fixed at around android api 4.1 but still we have to use android api 3.1 and other android api levels on Jmonkey so the problem persists for us.

This is what provides us with the game pad information after and only after the user interacts with the gamepad.

So, this only affects polling or it affects being able to query what joysticks there are?

If it’s only affecting polling before the first values are received then it seems like you could just return default values until the first are received.

I’m going to use ^this solution then. But just to remind you, when implementing your solution the program won’t know the gamepad Id or even if there is even a gamepad attached to the android computer until the user interacts with the gamepad.

Edit: Also, what default values do you want me to return. The gamepad Ids even change over time according to google’s sample programs, so there are no values (nada, zero, zilch) to be returned before the user interacts with the gamepad.

@Pixelapp said: I'm going to use ^this solution then. But just to remind you, when implementing your solution the program won't know the gamepad Id or even if there is even a gamepad attached to the android computer until the user interacts with the gamepad.

Edit: Also, what default values do you want me to return. The gamepad Ids even change over time according to google’s sample programs, so there are no values (nada, zero, zilch) to be returned before the user interacts with the gamepad.

I’m just asking questions and then providing possible solutions given a subset of potential answers because information is a bit scarce without educating myself at other links.

So, the answer is “They gamepad doesn’t even exist until the user interacts with it.” It can’t be polled. It can’t be detected, etc… JME would have no idea that there even is a gamepad connected until the user moves it.

I agree, that’s bad. There is no default set. I think we shouldn’t support gamepads unless the Android version is sufficiently high to support them for real. Otherwise, the app won’t even be able to adjust itself properly to the presence or non-presence of the device… since it doesn’t even exist.

Again… this all presumes that I’m now interpreting what you are saying correct. All of the above is null and void if I’m still not understanding.

It is not a problem for me so I’ll create the classes and place them in the contribution section of Jmonkey at the google code source. If people want to use them, then they should use them. I know I’ll be using them!

@Pixelapp said: It is not a problem for me so I'll create the classes and place them in the contribution section of Jmonkey at the google code source. If people want to use them, then they should use them. I know I'll be using them!

Don’t get me wrong, I think it’s great.

Just wondering if there is a way we can conditionally trigger the code based on android version so that at least we get proper “new hotness” when it fully works.

What I’m on my way to do is to implement the android 3.1 gamepad support with no inputManager class (i.e. doesn’t have gamepad initializer). This code source will be optional for jmonkey users to use. Then after I’m done coding that I’ll develop android 4.1 gamepad support with inputManager class (i.e. has with gamepad initializer) which will definitely (I hope) to be added to the Jmonkey engine’s core library.

1 Like

@Pixelapp : Most new devices have Android 4.1 or higher now. I don’t think it’s a big problem. I’m looking forward to your contribution. XD

Here’s the TestJoystick.java demo. It was minimally modified in order to accommodate android. It works for PC and android now. Though the android part of it only supports the left joystick for now. All other android gamepad activity is ignored.

I’m posting this unfinished code so you can glance at it, and give me tips on what to do next if you have a great idea. Let me know in the if you guys/gals are liking what you see. This project is almost finished so this is your time to make your voices be heard.

You still have to add [java]joystickEventsEnabled = true; // Enable joystick[/java] to your android activity in order for this to work. This might not be needed in future updates though.

[java]

package com.jme3test.input;

import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapText;
import com.jme3.input.Joystick;
import com.jme3.input.Joystick;
import com.jme3.input.JoystickAxis;
import com.jme3.input.JoystickAxis;
import com.jme3.input.JoystickButton;
import com.jme3.input.JoystickButton;
import com.jme3.input.RawInputListener;
import com.jme3.input.RawInputListener;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Quad;
import com.jme3.system.AppSettings;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

public class TestJoystick extends SimpleApplication {

private Joystick viewedJoystick;
private GamepadView gamepad;
private Node joystickInfo;
private float yInfo = 0;

private boolean initialized = false;
private Joystick[] joysticks;

public static void main(String[] args){
    TestJoystick app = new TestJoystick();
    AppSettings settings = new AppSettings(true);
    settings.setUseJoysticks(true);
    app.setSettings(settings);
    app.start();
}

@Override
public void simpleInitApp() 
{
}

@Override
public void simpleUpdate(float tpf) 
{ 

    if(joysticks == null)
    {
        //System.out.println("joystick is null!");
        joysticks = inputManager.getJoysticks();
    }
    
    if(joysticks != null && !initialized)
    {
        initialized = true;

        int gamepadSize = cam.getHeight() / 2;
        float scale = gamepadSize / 512.0f;        
        gamepad = new GamepadView();       
        gamepad.setLocalTranslation( cam.getWidth() - gamepadSize - (scale * 20), 0, 0 );
        gamepad.setLocalScale( scale, scale, scale ); 
        guiNode.attachChild(gamepad); 

        joystickInfo = new Node( "joystickInfo" );
        joystickInfo.setLocalTranslation( 0, cam.getHeight(), 0 );
        guiNode.attachChild( joystickInfo );

        // Add a raw listener because it's eisier to get all joystick events
        // this way.
        inputManager.addRawInputListener( new JoystickEventListener() );
    }
}

protected void addInfo( String info, int column ) {

    BitmapText t = new BitmapText(guiFont);
    t.setText( info );
    t.setLocalTranslation( column * 200, yInfo, 0 );
    joystickInfo.attachChild(t);
    yInfo -= t.getHeight();
}

protected void setViewedJoystick( Joystick stick ) {
    if( this.viewedJoystick == stick )
        return;

    if( this.viewedJoystick != null ) {
        joystickInfo.detachAllChildren();
    }
               
    this.viewedJoystick = stick;

    if( this.viewedJoystick != null ) {       
        // Draw the hud
        yInfo = 0;

        addInfo(  "Joystick:\"" + stick.getName() + "\"  id:" + stick.getJoyId(), 0 );

        yInfo -= 5;
                   
        float ySave = yInfo;
        
        // Column one for the buttons
        addInfo( "Buttons:", 0 );
        for( JoystickButton b : stick.getButtons() ) {
            addInfo( " '" + b.getName() + "' id:'" + b.getLogicalId() + "'", 0 );
        }
        yInfo = ySave;
        
        // Column two for the axes
        addInfo( "Axes:", 1 );
        for( JoystickAxis a : stick.getAxes() ) {
            addInfo( " '" + a.getName() + "' id:'" + a.getLogicalId() + "' analog:" + a.isAnalog(), 1 );
        }
        
    } 
}

/**
 *  Easier to watch for all button and axis events with a raw input listener.
 */   
protected class JoystickEventListener implements RawInputListener {

    public void onJoyAxisEvent(JoyAxisEvent evt) {
        setViewedJoystick( evt.getAxis().getJoystick() );
        gamepad.setAxisValue( evt.getAxis(), evt.getValue() ); 
    }

    public void onJoyButtonEvent(JoyButtonEvent evt) {
        setViewedJoystick( evt.getButton().getJoystick() );
        gamepad.setButtonValue( evt.getButton(), evt.isPressed() ); 
    }

    public void beginInput() {}
    public void endInput() {}
    public void onMouseMotionEvent(MouseMotionEvent evt) {}
    public void onMouseButtonEvent(MouseButtonEvent evt) {}
    public void onKeyEvent(KeyInputEvent evt) {}
    public void onTouchEvent(TouchEvent evt) {}        
}

protected class GamepadView extends Node {

    float xAxis = 0;
    float yAxis = 0;
    float zAxis = 0;
    float zRotation = 0;
    
    float lastPovX = 0;
    float lastPovY = 0;

    Geometry leftStick;
    Geometry rightStick;
        
    Map<String, ButtonView> buttons = new HashMap<String, ButtonView>();

    public GamepadView() {
        super( "gamepad" );

        // Sizes naturally for the texture size.  All positions will
        // be in that space because it's easier.
        int size = 512;

        Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        m.setTexture( "ColorMap", assetManager.loadTexture( "Interface/Joystick/gamepad-buttons.png" ) );
        m.getAdditionalRenderState().setBlendMode( BlendMode.Alpha ); 
        Geometry buttonPanel = new Geometry( "buttons", new Quad(size, size) );
        buttonPanel.setLocalTranslation( 0, 0, -1 );
        buttonPanel.setMaterial(m);
        attachChild(buttonPanel);
    
        m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        m.setTexture( "ColorMap", assetManager.loadTexture( "Interface/Joystick/gamepad-frame.png" ) );
        m.getAdditionalRenderState().setBlendMode( BlendMode.Alpha ); 
        Geometry frame = new Geometry( "frame", new Quad(size, size) );
        frame.setMaterial(m);
        attachChild(frame);
        
        m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        m.setTexture( "ColorMap", assetManager.loadTexture( "Interface/Joystick/gamepad-stick.png" ) );
        m.getAdditionalRenderState().setBlendMode( BlendMode.Alpha ); 
        leftStick = new Geometry( "leftStick", new Quad(64, 64) );
        leftStick.setMaterial(m);
        attachChild(leftStick);
        rightStick = new Geometry( "leftStick", new Quad(64, 64) );
        rightStick.setMaterial(m);
        attachChild(rightStick);

        // A "standard" mapping... fits a majority of my game pads
        addButton( JoystickButton.BUTTON_0, 371, 512 - 176, 42, 42 );
        addButton( JoystickButton.BUTTON_1, 407, 512 - 212, 42, 42 );
        addButton( JoystickButton.BUTTON_2, 371, 512 - 248, 42, 42 );
        addButton( JoystickButton.BUTTON_3, 334, 512 - 212, 42, 42 );

        // Front buttons  Some of these have the top ones and the bottoms ones flipped.           
        addButton( JoystickButton.BUTTON_4, 67, 512 - 111, 95, 21 );
        addButton( JoystickButton.BUTTON_5, 348, 512 - 111, 95, 21 );
        addButton( JoystickButton.BUTTON_6, 67, 512 - 89, 95, 21 );
        addButton( JoystickButton.BUTTON_7, 348, 512 - 89, 95, 21 );

        // Select and start buttons           
        addButton( JoystickButton.BUTTON_8, 206, 512 - 198, 48, 30 );
        addButton( JoystickButton.BUTTON_9, 262, 512 - 198, 48, 30 );
        
        // Joystick push buttons
        addButton( JoystickButton.BUTTON_10, 147, 512 - 300, 75, 70 );
        addButton( JoystickButton.BUTTON_11, 285, 512 - 300, 75, 70 );

        // Fake button highlights for the POV axes
        //
        //    +Y  
        //  -X  +X
        //    -Y
        //
        addButton( "POV +Y", 96, 512 - 174, 40, 38 );
        addButton( "POV +X", 128, 512 - 208, 40, 38 );
        addButton( "POV -Y", 96, 512 - 239, 40, 38 );
        addButton( "POV -X", 65, 512 - 208, 40, 38 );

        resetPositions();                                               
    }

    private void addButton( String name, float x, float y, float width, float height ) {
        ButtonView b = new ButtonView(name, x, y, width, height);
        attachChild(b);
        buttons.put(name, b);
    }

    public void setAxisValue( JoystickAxis axis, float value ) {
        System.out.println( "Axis:" + axis.getName() + "=" + value );
        if( axis == axis.getJoystick().getXAxis() ) {
            setXAxis(value);
        } else if( axis == axis.getJoystick().getYAxis() ) {
            setYAxis(-value);
        } else if( axis == axis.getJoystick().getAxis(JoystickAxis.Z_AXIS) ) {
            // Note: in the above condition, we could check the axis name but
            //       I have at least one joystick that reports 2 "Z Axis" axes.
            //       In this particular case, the first one is the right one so
            //       a name based lookup will find the proper one.  It's a problem
            //       because the erroneous axis sends a constant stream of values.
            setZAxis(value);
        } else if( axis == axis.getJoystick().getAxis(JoystickAxis.Z_ROTATION) ) {
            setZRotation(-value);
        } else if( axis == axis.getJoystick().getPovXAxis() ) {
            if( lastPovX < 0 ) {
                setButtonValue( "POV -X", false );    
            } else if( lastPovX > 0 ) {
                setButtonValue( "POV +X", false );    
            } 
            if( value < 0 ) {
                setButtonValue( "POV -X", true );    
            } else if( value > 0 ) {
                setButtonValue( "POV +X", true );    
            }
            lastPovX = value; 
        } else if( axis == axis.getJoystick().getPovYAxis() ) {
            if( lastPovY < 0 ) {
                setButtonValue( "POV -Y", false );    
            } else if( lastPovY > 0 ) {
                setButtonValue( "POV +Y", false );    
            } 
            if( value < 0 ) {
                setButtonValue( "POV -Y", true );    
            } else if( value > 0 ) {
                setButtonValue( "POV +Y", true );    
            }
            lastPovY = value; 
        }
    }

    public void setButtonValue( JoystickButton button, boolean isPressed ) {
        System.out.println( "Button:" + button.getName() + "=" + (isPressed ? "Down" : "Up") );
        setButtonValue( button.getLogicalId(), isPressed );
    }

    protected void setButtonValue( String name, boolean isPressed ) {
        ButtonView view = buttons.get(name);
        if( view != null ) {
            if( isPressed ) {
                view.down();
            } else {
                view.up();
            }
        }
    }           

    public void setXAxis( float f ) {
        xAxis = f;
        resetPositions();           
    }

    public void setYAxis( float f ) {
        yAxis = f;
        resetPositions();           
    }

    public void setZAxis( float f ) {
        zAxis = f;
        resetPositions();
    }

    public void setZRotation( float f ) {
        zRotation = f;
        resetPositions();
    }
        
    private void resetPositions() {

        float xBase = 155;
        float yBase = 212;
        
        Vector2f dir = new Vector2f(xAxis, yAxis);
        float length = Math.min(1, dir.length());
        dir.normalizeLocal();
        
        float angle = dir.getAngle();
        float x = FastMath.cos(angle) * length * 10;
        float y = FastMath.sin(angle) * length * 10;  
        leftStick.setLocalTranslation( xBase + x, yBase + y, 0 );
        
         
        xBase = 291;
        dir = new Vector2f(zAxis, zRotation);
        length = Math.min(1, dir.length());
        dir.normalizeLocal();
        
        angle = dir.getAngle();
        x = FastMath.cos(angle) * length * 10;
        y = FastMath.sin(angle) * length * 10;  
        rightStick.setLocalTranslation( xBase + x, yBase + y, 0 );
    }
}

protected class ButtonView extends Node {

    private int state = 0;
    private Material material;
    private ColorRGBA hilite = new ColorRGBA( 0.0f, 0.75f, 0.75f, 0.5f );
    
    public ButtonView( String name, float x, float y, float width, float height ) {
        super( "Button:" + name );
        setLocalTranslation( x, y, -0.5f );
        
        material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        material.setColor( "Color", hilite );
        material.getAdditionalRenderState().setBlendMode( BlendMode.Alpha ); 

        Geometry g = new Geometry( "highlight", new Quad(width, height) );
        g.setMaterial(material); 
        attachChild(g);
        
        resetState();            
    }

    private void resetState() {
        if( state <= 0 ) {
            setCullHint( CullHint.Always );
        } else {
            setCullHint( CullHint.Dynamic );
        }
        
        System.out.println( getName() + " state:" + state );
    }
    
    public void down() {
        state++;            
        resetState();
    }
    
    public void up() {
        state--;
        resetState();
    } 
}

}

[/java]

…I didn’t have time to diff it… what was changed for android?

I erased the simpleInitApp() method and added it to the simpleUpdate() method as shown below. So now initialization is in the update method instead of the init method.

[java]
@Override
public void simpleUpdate(float tpf)
{

    if(joysticks == null) // This is new
    {
        //System.out.println("joystick is null!");
        joysticks = inputManager.getJoysticks(); // This is new
    }
    
    if(joysticks != null && !initialized) // This is new
    {
        initialized = true; // This is new
        ...

    }
}

[/java]

@Pixelapp said: I erased the simpleInitApp() method and added it to the simpleUpdate() method as shown below. So now initialization is in the update method instead of the init method.

[java]
@Override
public void simpleUpdate(float tpf)
{

    if(joysticks == null) // This is new
    {
        //System.out.println("joystick is null!");
        joysticks = inputManager.getJoysticks(); // This is new
    }
    
    if(joysticks != null && !initialized) // This is new
    {
        initialized = true; // This is new
        ...

    }
}

[/java]

Why is this necessary again? What happens between simpleInit() and simpleUpdate() that matters?

If I write inside the simpleinit() then the program gets null for the gamepad. Why? Because you don’t know if the gamepad exists until the user interacts with the gamepad.