package cgeo.geocaching.sensors;

import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.RxUtils;

import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Subscriber;
import rx.functions.Action0;
import rx.subscriptions.Subscriptions;

import android.annotation.TargetApi;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

public class RotationProvider {

    private RotationProvider() {
        // Utility class, not to be instantiated
    }

    @TargetApi(19)
    public static Observable<Float> create(final Context context) {
        final SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
        final Sensor rotationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
        if (rotationSensor == null) {
            Log.w("RotationProvider: no rotation sensor on this device");
            return Observable.error(new RuntimeException("no rotation sensor"));
        }
        Log.d("RotationProvider: sensor found");
        final Observable<Float> observable = Observable.create(new OnSubscribe<Float>() {

            @Override
            public void call(final Subscriber<? super Float> subscriber) {
                final SensorEventListener listener = new SensorEventListener() {

                    private final float[] rotationMatrix = new float[16];
                    private final float[] orientation = new float[4];
                    private final float[] values = new float[4];

                    @Override
                    public void onSensorChanged(final SensorEvent event) {
                        // On some Samsung devices, SensorManager#getRotationMatrixFromVector throws an exception if the rotation
                        // vector has more than 4 elements. Since only the four first elements are used, we can truncate the vector
                        // without losing precision.
                        if (event.values.length > 4) {
                            System.arraycopy(event.values, 0, values, 0, 4);
                            SensorManager.getRotationMatrixFromVector(rotationMatrix, values);
                        } else {
                            SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values);
                        }
                        SensorManager.getOrientation(rotationMatrix, orientation);
                        subscriber.onNext((float) (orientation[0] * 180 / Math.PI));
                    }

                    @Override
                    public void onAccuracyChanged(final Sensor sensor, final int accuracy) {
                    }

                };
                Log.d("RotationProvider: registering listener");
                sensorManager.registerListener(listener, rotationSensor, SensorManager.SENSOR_DELAY_NORMAL);
                subscriber.add(Subscriptions.create(new Action0() {
                    @Override
                    public void call() {
                        RxUtils.looperCallbacksWorker.schedule(new Action0() {
                            @Override
                            public void call() {
                                Log.d("RotationProvider: unregistering listener");
                                sensorManager.unregisterListener(listener, rotationSensor);
                            }
                        });
                    }
                }));
            }
        });
        return observable.subscribeOn(RxUtils.looperCallbacksScheduler).share().onBackpressureLatest();
    }

    public static boolean hasRotationSensor(final Context context) {
        return ((SensorManager) context.getSystemService(Context.SENSOR_SERVICE)).getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) != null;
    }

}

@@ -499,24 +499,670 @@
 Provider %7B%0A%0A
+    /**%0A     * On some Samsung devices, %7B@link SensorManager#getRotationMatrixFromVector%7D throws an exception if the rotation%0A     * vector has more than 4 elements.%0A     * %3Cp/%3E%0A     * This will be detected and remembered after the first occurrence of the exception. Concurrent access%0A     * is not a problem as this variable can only go from %7B@code false%7D to %7B@code true%7D and being %7B@code false%7D%0A     * instead of %7B@code true%7D is innocuous and will be changed immediately when needed.%0A     *%0A     * @see %3Ca href=%22http://stackoverflow.com/a/22138449%22%3Ethis Stack Overflow answer%3C/a%3E%0A     */%0A    private static boolean isTruncationNeeded = false;%0A%0A
     private 
@@ -2399,173 +2399,64 @@
     
-// On some Samsung devices, SensorManager#getRotationMatrixFromVector throws an exception if the rotation%0A                        // vector has more than 4 elements.
+if (isTruncationNeeded) %7B%0A                            //
  Sin
@@ -2499,16 +2499,27 @@
 used
+ (and accepted)
 , we
- can
  tru
@@ -2538,116 +2538,9 @@
 ctor
-%0A                        // without losing precision.%0A                        if (event.values.length %3E 4) %7B
+.
 %0A   
@@ -2741,16 +2741,54 @@
  else %7B%0A
+                            try %7B%0A    
         
@@ -2872,32 +2872,475 @@
  event.values);%0A
+                            %7D catch (final IllegalArgumentException ignored) %7B%0A                                Log.d(%22installing workaround for mismatched number of values in rotation vector%22);%0A                                // Install workaround and retry%0A                                isTruncationNeeded = true;%0A                                onSensorChanged(event);%0A                                return;%0A                            %7D%0A
                 

Apply the workaround for Samsung devices only when strictly needed