aboutsummaryrefslogtreecommitdiffstats
path: root/main/src/cgeo/geocaching/sensors/DirectionProvider.java
blob: 5b17df6edd9718ece4780535d56b5dbf58d16c8c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package cgeo.geocaching.sensors;

import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.utils.AngleUtils;
import cgeo.geocaching.utils.StartableHandlerThread;

import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Subscriber;
import rx.subjects.BehaviorSubject;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Process;
import android.view.Surface;
import android.view.WindowManager;

public class DirectionProvider {

    private static final BehaviorSubject<Float> subject = BehaviorSubject.create(0.0f);

    private static final WindowManager windowManager = (WindowManager) CgeoApplication.getInstance().getSystemService(Context.WINDOW_SERVICE);

    static class Listener implements SensorEventListener, StartableHandlerThread.Callback {

        private int count = 0;

        private SensorManager sensorManager;

        @Override
        public void onSensorChanged(final SensorEvent event) {
            subject.onNext(event.values[0]);
        }

        @Override
        public void onAccuracyChanged(final Sensor sensor, final int accuracy) {
                /*
                * There is a bug in Android, which apparently causes this method to be called every
                * time the sensor _value_ changed, even if the _accuracy_ did not change. So logging
                * this event leads to the log being flooded with multiple entries _per second_,
                * which I experienced when running cgeo in a building (with GPS and network being
                * unreliable).
                *
                * See for example https://code.google.com/p/android/issues/detail?id=14792
                */

            //Log.i(Settings.tag, "Compass' accuracy is low (" + accuracy + ")");
        }

        @Override
        public void start(final Context context, final Handler handler) {
            if (!hasSensor(context)) {
                return;
            }
            if (++count == 1) {
                Sensor orientationSensor = getOrientationSensor(context);
                sensorManager.registerListener(this, orientationSensor, SensorManager.SENSOR_DELAY_NORMAL, handler);
            }
        }

        @Override
        public void stop() {
            if (!hasSensor) {
                return;
            }
            if (--count == 0) {
                sensorManager.unregisterListener(this);
            }
        }

        /**
         * Assume that there is an orientation sensor, unless we have really checked that
         */
        private boolean hasSensor = true;

        /**
         * Flag for one time check if there is a sensor.
         */
        private boolean hasSensorChecked = false;

        public boolean hasSensor(Context context) {
            if (hasSensorChecked == false) {
                hasSensor = getOrientationSensor(context) != null;
            }
            return hasSensor;
        }

        // This will be removed when using a new location service. Until then, it is okay to be used.
        @SuppressWarnings("deprecation")
        private Sensor getOrientationSensor(final Context context) {
            sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
            return sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
        }

    }

    private static final StartableHandlerThread handlerThread =
            new StartableHandlerThread("DirectionProvider thread", Process.THREAD_PRIORITY_BACKGROUND, new Listener());

    static {
      handlerThread.start();
    }
    static public Observable<Float> create(final Context context) {
        return Observable.create(new OnSubscribe<Float>() {
            @Override
            public void call(final Subscriber<? super Float> subscriber) {
                handlerThread.start(subscriber, context);
                subject.subscribe(subscriber);
            }
        });
    }

    /**
     * Take the phone rotation (through a given activity) in account and adjust the direction.
     *
     * @param direction the unadjusted direction in degrees, in the [0, 360[ range
     * @return the adjusted direction in degrees, in the [0, 360[ range
     */

    public static float getDirectionNow(final float direction) {
        return AngleUtils.normalize(direction + getRotationOffset());
    }

    static float reverseDirectionNow(final float direction) {
        return AngleUtils.normalize(direction - getRotationOffset());
    }

    private static int getRotationOffset() {
        switch (windowManager.getDefaultDisplay().getRotation()) {
            case Surface.ROTATION_90:
                return 90;
            case Surface.ROTATION_180:
                return 180;
            case Surface.ROTATION_270:
                return 270;
            default:
                return 0;
        }
    }

}