Xbox 360 controller not recognized on Android 4.0

Hello.

I’m using the jme3test.input.TestJoystick.java and when I run it on my nexus 7 tablet it doesn’t find my Xbox 360 controller through USB.

My controller works because I’m able to use it to scroll through menus and start my apps by pushing the buttons. Also, my controller works fine on my windows computer. Furthermore, I’m compiling against android 4.0. and 4.2.2

Any ideas on what’s the problem?

Right now, Joysticks are used to support using the android sensors. Sensor input is converted into Joystick Axes. I think this would have to be removed in order to get external joysticks to work. Also, we’d have some work to do to support real joysticks on Android.

If you want to look into it, feel free. If you get something working, let me know.

The code that converts the sensor input to joystick data is in package com.jme3.input.android in the source → android folder

@iwgeric said: Right now, Joysticks are used to support using the android sensors. Sensor input is converted into Joystick Axes. I think this would have to be removed in order to get external joysticks to work. Also, we'd have some work to do to support real joysticks on Android.

If you want to look into it, feel free. If you get something working, let me know.

The code that converts the sensor input to joystick data is in package com.jme3.input.android in the source → android folder

You don’t have to remove your existing joystick support to support real joysticks. The input manager will just report that it has more than one joystick… no big deal.

Real joystick support would need to be added, though.

I’ll try to work on this.

@pspeed said: You don't have to remove your existing joystick support to support real joysticks. The input manager will just report that it has more than one joystick... no big deal.

True enough, but the way it is implemented today, as soon as you enable joysticks, flycam will start to react to the android sensors. That should probably be changed to be optional so that if you want a real joystick, the sensors don’t come along for the ride unless configured to do so. You can extend flycam or do your own so that won’t be a problem, but the default should probably be a seperate option to enable the sensors instead of automatically using them when joysticks are enabled.

@Pixelapp said: I'll try to work on this.

OGLESContext has the getJoyInput() method that returns the joysticks. You’ll want to modify that to return yours instead of AndroidSensorJoyInput. That’d be the easiest way for now.

@iwgeric said: True enough, but the way it is implemented today, as soon as you enable joysticks, flycam will start to react to the android sensors. That should probably be changed to be optional so that if you want a real joystick, the sensors don't come along for the ride unless configured to do so. You can extend flycam or do your own so that won't be a problem, but the default should probably be a seperate option to enable the sensors instead of automatically using them when joysticks are enabled.

As I recall, flycam registers itself for all joysticks which is pretty dumb in general. But there is nothing to fly cam so I strongly encourage real games to write their own. FlyCam is good for demos and then it’s ok if it registers for all inputs. Or we can modify fly cam to take a joystick it should look for… but really, fly cam is so simple and unextensible that I hesitate to “fix it” in these little ways.

Hello. For those who have been following, this is the entire code for using a gamepad on android is below. This code is located at /samples/android-rev/ApiDemos/src/com/example/android/apis/view/GameControllerInput.java on your android sdk folder. I pasted this in case you can get it working faster than I can. :slight_smile: Nothing more than this is needed to get gamepads working on android.

You can easily get this code working on eclipse by setting and sample android 3.1 (api 12) project from your sdk from the aforementioned address.

[java]

/*

  • Copyright © 2011 The Android Open Source Project
  • Licensed under the Apache License, Version 2.0 (the “License”);
  • you may not use this file except in compliance with the License.
  • You may obtain a copy of the License at
  •  http://www.apache.org/licenses/LICENSE-2.0
    
  • Unless required by applicable law or agreed to in writing, software
  • distributed under the License is distributed on an “AS IS” BASIS,
  • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  • See the License for the specific language governing permissions and
  • limitations under the License.
    */

package com.example.android.apis.view;

import com.example.android.apis.R;

import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.InputDevice.MotionRange;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicLong;

/**

  • Demonstrates how to process input events received from game controllers.

  • This activity displays button states and joystick positions.

  • Also writes detailed information about relevant input events to the log.

  • The game controller is also uses to control a very simple game. See {@link GameView}

  • for the game itself.
    */
    public class GameControllerInput extends Activity {
    private static final String TAG = “GameControllerInput”;

    private SparseArray<InputDeviceState> mInputDeviceStates;
    private GameView mGame;
    private ListView mSummaryList;
    private SummaryAdapter mSummaryAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

     mInputDeviceStates = new SparseArray&lt;InputDeviceState&gt;();
     mSummaryAdapter = new SummaryAdapter(this, getResources());
    
     setContentView(R.layout.game_controller_input);
    
     mGame = (GameView) findViewById(R.id.game);
    
     mSummaryList = (ListView) findViewById(R.id.summary);
     mSummaryList.setAdapter(mSummaryAdapter);
     mSummaryList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
         @Override
         public void onItemClick(AdapterView&lt;?&gt; parent, View view, int position, long id) {
             mSummaryAdapter.onItemClick(position);
         }
     });
    

    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);

     mGame.requestFocus();
    

    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
    // Update device state for visualization and logging.
    InputDeviceState state = getInputDeviceState(event);
    if (state != null) {
    switch (event.getAction()) {
    case KeyEvent.ACTION_DOWN:
    if (state.onKeyDown(event)) {
    mSummaryAdapter.show(state);
    }
    break;
    case KeyEvent.ACTION_UP:
    if (state.onKeyUp(event)) {
    mSummaryAdapter.show(state);
    }
    break;
    }
    }
    return super.dispatchKeyEvent(event);
    }

    @Override
    public boolean dispatchGenericMotionEvent(MotionEvent event) {
    // Check that the event came from a joystick since a generic motion event
    // could be almost anything.
    if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0
    && event.getAction() == MotionEvent.ACTION_MOVE) {
    // Update device state for visualization and logging.
    InputDeviceState state = getInputDeviceState(event);
    if (state != null && state.onJoystickMotion(event)) {
    mSummaryAdapter.show(state);
    }
    }
    return super.dispatchGenericMotionEvent(event);
    }

    private InputDeviceState getInputDeviceState(InputEvent event) {
    final int deviceId = event.getDeviceId();
    InputDeviceState state = mInputDeviceStates.get(deviceId);
    if (state == null) {
    final InputDevice device = event.getDevice();
    if (device == null) {
    return null;
    }
    state = new InputDeviceState(device);
    mInputDeviceStates.put(deviceId, state);

         Log.i(TAG, device.toString());
     }
     return state;
    

    }

    /**

    • Tracks the state of joystick axes and game controller buttons for a particular

    • input device for diagnostic purposes.
      */
      private static class InputDeviceState {
      private final InputDevice mDevice;
      private final int[] mAxes;
      private final float[] mAxisValues;
      private final SparseIntArray mKeys;

      public InputDeviceState(InputDevice device) {
      mDevice = device;

       int numAxes = 0;
       for (MotionRange range : device.getMotionRanges()) {
           if ((range.getSource() &amp; InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
               numAxes += 1;
           }
       }
      
       mAxes = new int[numAxes];
       mAxisValues = new float[numAxes];
       int i = 0;
       for (MotionRange range : device.getMotionRanges()) {
           if ((range.getSource() &amp; InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
               numAxes += 1;
           }
           mAxes[i++] = range.getAxis();
       }
      
       mKeys = new SparseIntArray();
      

      }

      public InputDevice getDevice() {
      return mDevice;
      }

      public int getAxisCount() {
      return mAxes.length;
      }

      public int getAxis(int axisIndex) {
      return mAxes[axisIndex];
      }

      public float getAxisValue(int axisIndex) {
      return mAxisValues[axisIndex];
      }

      public int getKeyCount() {
      return mKeys.size();
      }

      public int getKeyCode(int keyIndex) {
      return mKeys.keyAt(keyIndex);
      }

      public boolean isKeyPressed(int keyIndex) {
      return mKeys.valueAt(keyIndex) != 0;
      }

      public boolean onKeyDown(KeyEvent event) {
      final int keyCode = event.getKeyCode();
      if (isGameKey(keyCode)) {
      if (event.getRepeatCount() == 0) {
      final String symbolicName = KeyEvent.keyCodeToString(keyCode);
      mKeys.put(keyCode, 1);
      Log.i(TAG, mDevice.getName() + " - Key Down: " + symbolicName);
      }
      return true;
      }
      return false;
      }

      public boolean onKeyUp(KeyEvent event) {
      final int keyCode = event.getKeyCode();
      if (isGameKey(keyCode)) {
      int index = mKeys.indexOfKey(keyCode);
      if (index >= 0) {
      final String symbolicName = KeyEvent.keyCodeToString(keyCode);
      mKeys.put(keyCode, 0);
      Log.i(TAG, mDevice.getName() + " - Key Up: " + symbolicName);
      }
      return true;
      }
      return false;
      }

      public boolean onJoystickMotion(MotionEvent event) {
      StringBuilder message = new StringBuilder();
      message.append(mDevice.getName()).append(" - Joystick Motion:\n");

       final int historySize = event.getHistorySize();
       for (int i = 0; i &lt; mAxes.length; i++) {
           final int axis = mAxes[i];
           final float value = event.getAxisValue(axis);
           mAxisValues[i] = value;
           message.append("  ").append(MotionEvent.axisToString(axis)).append(": ");
      
           // Append all historical values in the batch.
           for (int historyPos = 0; historyPos &lt; historySize; historyPos++) {
               message.append(event.getHistoricalAxisValue(axis, historyPos));
               message.append(", ");
           }
      
           // Append the current value.
           message.append(value);
           message.append("\n");
       }
       Log.i(TAG, message.toString());
       return true;
      

      }

      // Check whether this is a key we care about.
      // In a real game, we would probably let the user configure which keys to use
      // instead of hardcoding the keys like this.
      private static boolean isGameKey(int keyCode) {
      switch (keyCode) {
      case KeyEvent.KEYCODE_DPAD_UP:
      case KeyEvent.KEYCODE_DPAD_DOWN:
      case KeyEvent.KEYCODE_DPAD_LEFT:
      case KeyEvent.KEYCODE_DPAD_RIGHT:
      case KeyEvent.KEYCODE_DPAD_CENTER:
      case KeyEvent.KEYCODE_SPACE:
      return true;
      default:
      return KeyEvent.isGamepadButton(keyCode);
      }
      }
      }

    /**

    • A list adapter that displays a summary of the device state.
      */
      private static class SummaryAdapter extends BaseAdapter {
      private static final int BASE_ID_HEADING = 1 << 10;
      private static final int BASE_ID_DEVICE_ITEM = 2 << 10;
      private static final int BASE_ID_AXIS_ITEM = 3 << 10;
      private static final int BASE_ID_KEY_ITEM = 4 << 10;

      private final Context mContext;
      private final Resources mResources;

      private final SparseArray<Item> mDataItems = new SparseArray<Item>();
      private final ArrayList<Item> mVisibleItems = new ArrayList<Item>();

      private final Heading mDeviceHeading;
      private final TextColumn mDeviceNameTextColumn;

      private final Heading mAxesHeading;
      private final Heading mKeysHeading;

      private InputDeviceState mState;

      public SummaryAdapter(Context context, Resources resources) {
      mContext = context;
      mResources = resources;

       mDeviceHeading = new Heading(BASE_ID_HEADING | 0,
               mResources.getString(R.string.game_controller_input_heading_device));
       mDeviceNameTextColumn = new TextColumn(BASE_ID_DEVICE_ITEM | 0,
               mResources.getString(R.string.game_controller_input_label_device_name));
      
       mAxesHeading = new Heading(BASE_ID_HEADING | 1,
               mResources.getString(R.string.game_controller_input_heading_axes));
       mKeysHeading = new Heading(BASE_ID_HEADING | 2,
               mResources.getString(R.string.game_controller_input_heading_keys));
      

      }

      public void onItemClick(int position) {
      if (mState != null) {
      Toast toast = Toast.makeText(
      mContext, mState.getDevice().toString(), Toast.LENGTH_LONG);
      toast.show();
      }
      }

      public void show(InputDeviceState state) {
      mState = state;
      mVisibleItems.clear();

       // Populate device information.
       mVisibleItems.add(mDeviceHeading);
       mDeviceNameTextColumn.setContent(state.getDevice().getName());
       mVisibleItems.add(mDeviceNameTextColumn);
      
       // Populate axes.
       mVisibleItems.add(mAxesHeading);
       final int axisCount = state.getAxisCount();
       for (int i = 0; i &lt; axisCount; i++) {
           final int axis = state.getAxis(i);
           final int id = BASE_ID_AXIS_ITEM | axis;
           TextColumn column = (TextColumn) mDataItems.get(id);
           if (column == null) {
               column = new TextColumn(id, MotionEvent.axisToString(axis));
               mDataItems.put(id, column);
           }
           column.setContent(Float.toString(state.getAxisValue(i)));
           mVisibleItems.add(column);
       }
      
       // Populate keys.
       mVisibleItems.add(mKeysHeading);
       final int keyCount = state.getKeyCount();
       for (int i = 0; i &lt; keyCount; i++) {
           final int keyCode = state.getKeyCode(i);
           final int id = BASE_ID_KEY_ITEM | keyCode;
           TextColumn column = (TextColumn) mDataItems.get(id);
           if (column == null) {
               column = new TextColumn(id, KeyEvent.keyCodeToString(keyCode));
               mDataItems.put(id, column);
           }
           column.setContent(mResources.getString(state.isKeyPressed(i)
                   ? R.string.game_controller_input_key_pressed
                   : R.string.game_controller_input_key_released));
           mVisibleItems.add(column);
       }
      
       notifyDataSetChanged();
      

      }

      @Override
      public boolean hasStableIds() {
      return true;
      }

      @Override
      public int getCount() {
      return mVisibleItems.size();
      }

      @Override
      public Item getItem(int position) {
      return mVisibleItems.get(position);
      }

      @Override
      public long getItemId(int position) {
      return getItem(position).getItemId();
      }

      @Override
      public View getView(int position, View convertView, ViewGroup parent) {
      return getItem(position).getView(convertView, parent);
      }

      private static abstract class Item {
      private final int mItemId;
      private final int mLayoutResourceId;
      private View mView;

       public Item(int itemId, int layoutResourceId) {
           mItemId = itemId;
           mLayoutResourceId = layoutResourceId;
       }
      
       public long getItemId() {
           return mItemId;
       }
      
       public View getView(View convertView, ViewGroup parent) {
           if (mView == null) {
               LayoutInflater inflater = (LayoutInflater)
                       parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
               mView = inflater.inflate(mLayoutResourceId, parent, false);
               initView(mView);
           }
           updateView(mView);
           return mView;
       }
      
       protected void initView(View view) {
       }
      
       protected void updateView(View view) {
       }
      

      }

      private static class Heading extends Item {
      private final String mLabel;

       public Heading(int itemId, String label) {
           super(itemId, R.layout.game_controller_input_heading);
           mLabel = label;
       }
      
       @Override
       public void initView(View view) {
           TextView textView = (TextView) view;
           textView.setText(mLabel);
       }
      

      }

      private static class TextColumn extends Item {
      private final String mLabel;

       private String mContent;
       private TextView mContentView;
      
       public TextColumn(int itemId, String label) {
           super(itemId, R.layout.game_controller_input_text_column);
           mLabel = label;
       }
      
       public void setContent(String content) {
           mContent = content;
       }
      
       @Override
       public void initView(View view) {
           TextView textView = (TextView) view.findViewById(R.id.label);
           textView.setText(mLabel);
      
           mContentView = (TextView) view.findViewById(R.id.content);
       }
      
       @Override
       public void updateView(View view) {
           mContentView.setText(mContent);
       }
      

      }
      }
      }

[/java]

[Removed by user]

@Pixelapp

In MainActivity.java, make sure you set joystickEventsEnabled = true; If it is left false (default), joyInput will be set to null in Application.java and OGLESContext.getJoyInput(); will never be called.

Also, if you return null from getJoyInput, it has the same effect as not setting joystickEventsEnabled. The class implementing JoyInput will never be initialized and InputManager just ignores joysticks.

<cite>@iwgeric said:</cite> Also, if you return null from getJoyInput, it has the same effect as not setting joystickEventsEnabled. The class implementing JoyInput will never be initialized and InputManager just ignores joysticks.
That's what I'm saying, I set getJoyInput() to return null and the accelerometer still works so, getJoyInput() I think is not what I'm supposed to be working on.

Also, as I said, the accelerometer works so yes joystickEventsEnabled = true was on my main activity.

Any other ideas on what I have to do?

@Pixelapp said: That's what I'm saying, I set getJoyInput() to return null and the accelerometer still works

What exactly do you mean by “still works”? You’re getting jME input manager joystick events? Honestly, I don’t see how this could be possible if you are really running the new version of the OGLESContext that returns null for getJoyInput().

I assume you’re using Eclipse since you are copying the jme-android.jar to the mobile directory. Are you sure the new version of the jme-android.jar is being used? (don’t be insulted, just want to make sure).

I’m using JMonkey, No eclipse.

This is from Application.java in the initInput method:
if (!settings.getBoolean(“DisableJoysticks”)){
joyInput = context.getJoyInput();
if (joyInput != null)
joyInput.initialize();
}

    inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput);

I don’t see how the joystick events could be getting fired from input manager if you are returning null from OGLESContext.

“I assume you’re using Eclipse since you are copying the jme-android.jar to the mobile directory. Are you sure the new version of the jme-android.jar is being used? (don’t be insulted, just want to make sure).”

Yeah, that’s what I’m trying to figure out right now. I decompiled one jme3-android.jar and it was ok, I’ll do more checks now. I’ll be right back.

When I build the Accelerometer project on Jmonkey after I delete the stock JME3-android.jar, the stock JME3-android.jar reappears I don’t know from where it is coming.

If you are using the jMonkeyEngine SDK, every time the project runs, the mobile libs directory is wiped out and reloaded from the main project libraries. Then the jme-android.jar library is copied into the mobile/libs directory based on the jar file defined in the android-base global library. If you didn’t redefine the global library or override the target in build.xml, then the original jme-android.jar file from the sdk is copied into the mobile/libs directory.

Could that be happening?

Ok. The unmodified jme3-android is being built after I delete it. That’s the problem. If I could get that from being rebuilt I should be advancing.

Edit: What you said is what is happening. For your information I tend to use Eclipse as much as I use JMonkey so feel free to make suggestions about me using eclipse.

2 options.

1.) edit the android-base global library to point to your newly compiled jme-android.jar file
2.) create a new global library that includes your custom jme-android.jar file and edit build.xml to override the target and load your version instead of the default one.

Add the following to build.xml

<target name="-add-android-lib">
    <echo>Adding libraries for android.</echo>
    <copy todir="mobile/libs" flatten="true" preservelastmodified="true">
        <path>
            <pathelement path="${libs.my_global_library_with_my_custom_jme-android_jar _file.classpath}"/>
        </path>
    </copy>
</target>