Search code examples
androidandroid-sensors

Sensors TYPE_ACCELEROMETER and TYPE_MAGNETIC_FIELD - They work on phones, but doesn't on tablets


In my 2d game I have the following code which is responsible for game entity control (flying plane). It all seems to be working fine when it comes for phones, but unfortunately I've getting some information that on Android tablets the steering is completely unreliable (axis are messed up, or it doesn't work at all). Unfortunately I don't have a tablet of my own, so I cannot investigate it closer. So.. what's wrong with the following code? (for the clarity I put only the code related to sensors)

// ...
private float[] accelerometerValues;
private float[] magneticFieldValues;
private float[] R;
private float[] I;
private float[] outR;
private float[] sensorValues;

private Sensor accelerometer;
private Sensor magneticField;
// ...

// ... sensor initialization
    sensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
    if(sensorManager == null)
        return;
    accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 
    magneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
// ...

// ... onPause() sensor is being unregistered
public void onResume() {
    if(!sensorManager.registerListener(sensorListener, accelerometer, SensorManager.SENSOR_DELAY_GAME) ||
       !sensorManager.registerListener(sensorListener, magneticField, SensorManager.SENSOR_DELAY_GAME))
// ...

    // ...
    sensorListener = new SensorEventListener() {
        public void onAccuracyChanged(Sensor arg0, int arg1) {              
        }
        public void onSensorChanged(android.hardware.SensorEvent event) {
            synchronized(InputMgr.this) {
                switch(event.sensor.getType()) {
                case Sensor.TYPE_ACCELEROMETER:
                    System.arraycopy(event.values, 0, accelerometerValues, 0, 3);
                    break;
                case Sensor.TYPE_MAGNETIC_FIELD:
                    System.arraycopy(event.values, 0, magneticFieldValues, 0, 3);
                    break;
                }
            }
        }
    };  
    // ...  

    // used somewhere in the game
public void getSensorValues(float values[]) {
    synchronized(InputMgr.this) {
        SensorManager.getRotationMatrix(R, I, accelerometerValues, magneticFieldValues);
        SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_Y, SensorManager.AXIS_X, outR);
        SensorManager.getOrientation(outR, sensorValues);
        System.arraycopy(sensorValues, 0, values, 0, sensorValues.length);
    }
}

Solution

  • On devices whose default orientation is landscape (-> most tablets), the sensor values are kind of 'wrong' (I don't know why). So you need to catch those devices and remap your Rotation Matrix.

    To check whether the matrix needs to be remapped, you can use this code:

    public boolean needToRemapOrientationMatrix;
    
    // compute once (e.g. in onCreate() of your Activity):
    Display display = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
    int orientation;
    if(display.getWidth() < display.getHeight()) orientation = Configuration.ORIENTATION_PORTRAIT;
    else if(display.getWidth() > display.getHeight()) orientation = Configuration.ORIENTATION_LANDSCAPE;
    else orientation = Configuration.ORIENTATION_SQUARE;
    int rotation = display.getRotation();
    needToRemapOrientationMatrix =
        (orientation==Configuration.ORIENTATION_LANDSCAPE && (rotation==Surface.ROTATION_0 || rotation==Surface.ROTATION_180)) ||
        (orientation==Configuration.ORIENTATION_PORTRAIT && (rotation==Surface.ROTATION_90 || rotation==Surface.ROTATION_270));
    

    And when you read the sensor values, remap the matrix if needed:

    public void getSensorValues(float values[]) {
        synchronized(InputMgr.this) {
            SensorManager.getRotationMatrix(R, I, accelerometerValues, magneticFieldValues);
    
            if(needToRemapOrientationMatrix)
                SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_X, R);
    
            SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_Y, SensorManager.AXIS_X, outR);
            SensorManager.getOrientation(outR, sensorValues);
            System.arraycopy(sensorValues, 0, values, 0, sensorValues.length);
        }
    }
    

    This worked for me, I hope it helps.