Skip to content Skip to sidebar Skip to footer

How Can I Ensure Compatibility Between Android Api Levels For Rotationvector?

I have an Android app that's using the Rotation Vector sensor. Having read the comment here, I see that with newer API levels, the SensorEvent contains 4-5 values, instead of 3. M

Solution 1:

The post that you reference actually refers to a bug in a few Samsung devices (Galaxy S4, Galaxy Note 3) - see this Android Developer list post. You actually shouldn't have to do any special handling between SDK levels for this code to work on normal devices. But, alas, fragmentation...

Chromium handles this issue by truncating the array if the size is greater than 4:

if (values.length > 4) {
    // On some Samsung devices SensorManager.getRotationMatrixFromVector// appears to throw an exception if rotation vector has length > 4.// For the purposes of this class the first 4 values of the// rotation vector are sufficient (see crbug.com/335298 for details).if (mTruncatedRotationVector == null) {
        mTruncatedRotationVector = newfloat[4];
    }
    System.arraycopy(values, 0, mTruncatedRotationVector, 0, 4);
        getOrientationFromRotationVector(mTruncatedRotationVector);
} else {
    getOrientationFromRotationVector(values);
}

However, I found in my app GPSTest that this solution didn't seem to work on the Galaxy S3 (see Github issue here).

So, I ended up only truncating the array on devices that throw the IllegalArgumentException. This also avoids the extra System.arraycopy() unless its absolutely necessary.

Here's the code snippet (that also supports orientation sensors on devices with API levels less than Gingerbread (i.e., prior to when the ROTATION_VECTOR sensor was introduced), and handles remapping the coordinate system for orientation changes), which uses a class member mTruncateVector that is initialized to false:

@TargetApi(Build.VERSION_CODES.GINGERBREAD)
@Override
publicvoidonSensorChanged(SensorEvent event) {

    double orientation = Double.NaN;
    double tilt = Double.NaN;

    switch (event.sensor.getType()) {
        case Sensor.TYPE_ROTATION_VECTOR:
            // Modern rotation vector sensorsif (!mTruncateVector) {
                try {
                    SensorManager.getRotationMatrixFromVector(mRotationMatrix, event.values);
                } catch (IllegalArgumentException e) {
                    // On some Samsung devices, an exception is thrown if this vector > 4 (see #39)// Truncate the array, since we can deal with only the first four values
                    Log.e(TAG, "Samsung device error? Will truncate vectors - " + e);
                    mTruncateVector = true;
                    // Do the truncation here the first time the exception occurs
                    getRotationMatrixFromTruncatedVector(event.values);
                }
            } else {
                // Truncate the array to avoid the exception on some devices (see #39)
                getRotationMatrixFromTruncatedVector(event.values);
            }

            int rot = getWindowManager().getDefaultDisplay().getRotation();
            switch (rot) {
                case Surface.ROTATION_0:
                    // No orientation change, use default coordinate system
                    SensorManager.getOrientation(mRotationMatrix, mValues);
                    // Log.d(TAG, "Rotation-0");break;
                case Surface.ROTATION_90:
                    // Log.d(TAG, "Rotation-90");
                    SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_Y,
                            SensorManager.AXIS_MINUS_X, mRemappedMatrix);
                    SensorManager.getOrientation(mRemappedMatrix, mValues);
                    break;
                case Surface.ROTATION_180:
                    // Log.d(TAG, "Rotation-180");
                    SensorManager
                            .remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_MINUS_X,
                                    SensorManager.AXIS_MINUS_Y, mRemappedMatrix);
                    SensorManager.getOrientation(mRemappedMatrix, mValues);
                    break;
                case Surface.ROTATION_270:
                    // Log.d(TAG, "Rotation-270");
                    SensorManager
                            .remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_MINUS_Y,
                                    SensorManager.AXIS_X, mRemappedMatrix);
                    SensorManager.getOrientation(mRemappedMatrix, mValues);
                    break;
                default:
                    // This shouldn't happen - assume default orientation
                    SensorManager.getOrientation(mRotationMatrix, mValues);
                    // Log.d(TAG, "Rotation-Unknown");break;
            }
            orientation = Math.toDegrees(mValues[0]);  // azimuth
            tilt = Math.toDegrees(mValues[1]);
            break;
        case Sensor.TYPE_ORIENTATION:
            // Legacy orientation sensors
            orientation = event.values[0];
            break;
        default:
            // A sensor we're not using, so returnreturn;
    }
}

@TargetApi(Build.VERSION_CODES.GINGERBREAD)
privatevoidgetRotationMatrixFromTruncatedVector(float[] vector) {
    System.arraycopy(vector, 0, mTruncatedRotationVector, 0, 4);
    SensorManager.getRotationMatrixFromVector(mRotationMatrix, mTruncatedRotationVector);
}

and to register the sensors in onResume():

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
        // Use the modern rotation vector sensorsSensorvectorSensor= mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
        mSensorManager.registerListener(this, vectorSensor, 16000); // ~60hz
    } else {
        // Use the legacy orientation sensorsSensorsensor= mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
        if (sensor != null) {
            mSensorManager.registerListener(this, sensor,
                    SensorManager.SENSOR_DELAY_GAME);
        }
    }

Full implementation is here on Github.

Solution 2:

Short summary: Presuming you don't want to make use of values[3] and values[4], your code is fine as is.

From the docs:

values[0]: x*sin/2)
values[1]: y*sin/2)
values[2]: z*sin/2)
values[3]: cos/2)
values[4]: estimated heading Accuracy (in radians) (-1 if unavailable)
values[3], originally optional, will always be present from SDK Level 18 onwards. values[4] is a newvalue that has been added in SDK Level 18.

If I read that right, event.values.length will only be greater 3 if you compiled with SDK 18 or older.

SensorManager.getRotationMatrixFromVector seems to assume a rotation vector of length==3. I'm not certain what that function does if the rotation vector passed in is larger than 3 elements anyway.

If you ever needed to make use of the event.values[3] and event.values[4], you could detect if the device supports these extended values simply by checking the event.values.length. You could also check to see if Build.VERSION.SDK_INT is >= 18 at runtime as well. But if you don't need it, stick with your hardcoded assumption of 3.

Post a Comment for "How Can I Ensure Compatibility Between Android Api Levels For Rotationvector?"