How can I ensure compatibility between Android API levels for RotationVector?

后端 未结 2 877
时光取名叫无心
时光取名叫无心 2020-12-21 12:29

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

相关标签:
2条回答
  • 2020-12-21 12:42

    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 new value 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.

    0 讨论(0)
  • 2020-12-21 12:46

    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 = new float[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
    public void onSensorChanged(SensorEvent event) {
    
        double orientation = Double.NaN;
        double tilt = Double.NaN;
    
        switch (event.sensor.getType()) {
            case Sensor.TYPE_ROTATION_VECTOR:
                // Modern rotation vector sensors
                if (!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 return
                return;
        }
    }
    
    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    private void getRotationMatrixFromTruncatedVector(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 sensors
            Sensor vectorSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
            mSensorManager.registerListener(this, vectorSensor, 16000); // ~60hz
        } else {
            // Use the legacy orientation sensors
            Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
            if (sensor != null) {
                mSensorManager.registerListener(this, sensor,
                        SensorManager.SENSOR_DELAY_GAME);
            }
        }
    

    Full implementation is here on Github.

    0 讨论(0)
提交回复
热议问题