DeviceSensors.java revision 0529e5d033099cbfc42635f6f6183833b09dff6e
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.content.browser;
6
7import android.content.Context;
8import android.hardware.Sensor;
9import android.hardware.SensorEvent;
10import android.hardware.SensorEventListener;
11import android.hardware.SensorManager;
12import android.os.Handler;
13import android.os.HandlerThread;
14import android.util.Log;
15
16import com.google.common.annotations.VisibleForTesting;
17
18import org.chromium.base.CalledByNative;
19import org.chromium.base.CollectionUtil;
20import org.chromium.base.JNINamespace;
21import org.chromium.base.ThreadUtils;
22
23import java.util.HashSet;
24import java.util.List;
25import java.util.Set;
26import java.util.concurrent.Callable;
27
28/**
29 * Android implementation of the device motion and orientation APIs.
30 */
31@JNINamespace("content")
32class DeviceSensors implements SensorEventListener {
33
34    private static final String TAG = "DeviceMotionAndOrientation";
35
36    // These fields are lazily initialized by getHandler().
37    private Thread mThread;
38    private Handler mHandler;
39
40    // A reference to the application context in order to acquire the SensorService.
41    private final Context mAppContext;
42
43    // The lock to access the mHandler.
44    private final Object mHandlerLock = new Object();
45
46    // Non-zero if and only if we're listening for events.
47    // To avoid race conditions on the C++ side, access must be synchronized.
48    private long mNativePtr;
49
50    // The lock to access the mNativePtr.
51    private final Object mNativePtrLock = new Object();
52
53    // Holds a shortened version of the rotation vector for compatibility purposes.
54    private float[] mTruncatedRotationVector;
55
56    // Lazily initialized when registering for notifications.
57    private SensorManagerProxy mSensorManagerProxy;
58
59    // The only instance of that class and its associated lock.
60    private static DeviceSensors sSingleton;
61    private static Object sSingletonLock = new Object();
62
63    /**
64     * constants for using in JNI calls, also see
65     * content/browser/device_sensors/sensor_manager_android.cc
66     */
67    static final int DEVICE_ORIENTATION = 0;
68    static final int DEVICE_MOTION = 1;
69
70    static final Set<Integer> DEVICE_ORIENTATION_SENSORS = CollectionUtil.newHashSet(
71            Sensor.TYPE_ROTATION_VECTOR);
72
73    static final Set<Integer> DEVICE_MOTION_SENSORS = CollectionUtil.newHashSet(
74            Sensor.TYPE_ACCELEROMETER,
75            Sensor.TYPE_LINEAR_ACCELERATION,
76            Sensor.TYPE_GYROSCOPE);
77
78    @VisibleForTesting
79    final Set<Integer> mActiveSensors = new HashSet<Integer>();
80    boolean mDeviceMotionIsActive = false;
81    boolean mDeviceOrientationIsActive = false;
82
83    protected DeviceSensors(Context context) {
84        mAppContext = context.getApplicationContext();
85    }
86
87    /**
88     * Start listening for sensor events. If this object is already listening
89     * for events, the old callback is unregistered first.
90     *
91     * @param nativePtr Value to pass to nativeGotOrientation() for each event.
92     * @param rateInMilliseconds Requested callback rate in milliseconds. The
93     *            actual rate may be higher. Unwanted events should be ignored.
94     * @param eventType Type of event to listen to, can be either DEVICE_ORIENTATION or
95     *                  DEVICE_MOTION.
96     * @return True on success.
97     */
98    @CalledByNative
99    public boolean start(long nativePtr, int eventType, int rateInMilliseconds) {
100        boolean success = false;
101        synchronized (mNativePtrLock) {
102            switch (eventType) {
103                case DEVICE_ORIENTATION:
104                    success = registerSensors(DEVICE_ORIENTATION_SENSORS, rateInMilliseconds,
105                            true);
106                    break;
107                case DEVICE_MOTION:
108                    // note: device motion spec does not require all sensors to be available
109                    success = registerSensors(DEVICE_MOTION_SENSORS, rateInMilliseconds, false);
110                    break;
111                default:
112                    Log.e(TAG, "Unknown event type: " + eventType);
113                    return false;
114            }
115            if (success) {
116                mNativePtr = nativePtr;
117                setEventTypeActive(eventType, true);
118            }
119            return success;
120        }
121    }
122
123    @CalledByNative
124    public int getNumberActiveDeviceMotionSensors() {
125        Set<Integer> deviceMotionSensors = new HashSet<Integer>(DEVICE_MOTION_SENSORS);
126        deviceMotionSensors.removeAll(mActiveSensors);
127        return DEVICE_MOTION_SENSORS.size() - deviceMotionSensors.size();
128    }
129
130    /**
131     * Stop listening to sensors for a given event type. Ensures that sensors are not disabled
132     * if they are still in use by a different event type.
133     *
134     * @param eventType Type of event to listen to, can be either DEVICE_ORIENTATION or
135     *                  DEVICE_MOTION.
136     * We strictly guarantee that the corresponding native*() methods will not be called
137     * after this method returns.
138     */
139    @CalledByNative
140    public void stop(int eventType) {
141        Set<Integer> sensorsToRemainActive = new HashSet<Integer>();
142        synchronized (mNativePtrLock) {
143            switch (eventType) {
144                case DEVICE_ORIENTATION:
145                    if (mDeviceMotionIsActive) {
146                        sensorsToRemainActive.addAll(DEVICE_MOTION_SENSORS);
147                    }
148                    break;
149                case DEVICE_MOTION:
150                    if (mDeviceOrientationIsActive) {
151                        sensorsToRemainActive.addAll(DEVICE_ORIENTATION_SENSORS);
152                    }
153                    break;
154                default:
155                    Log.e(TAG, "Unknown event type: " + eventType);
156                    return;
157            }
158
159            Set<Integer> sensorsToDeactivate = new HashSet<Integer>(mActiveSensors);
160            sensorsToDeactivate.removeAll(sensorsToRemainActive);
161            unregisterSensors(sensorsToDeactivate);
162            setEventTypeActive(eventType, false);
163            if (mActiveSensors.isEmpty()) {
164                mNativePtr = 0;
165            }
166        }
167    }
168
169    @Override
170    public void onAccuracyChanged(Sensor sensor, int accuracy) {
171        // Nothing
172    }
173
174    @Override
175    public void onSensorChanged(SensorEvent event) {
176        sensorChanged(event.sensor.getType(), event.values);
177    }
178
179    @VisibleForTesting
180    void sensorChanged(int type, float[] values) {
181        switch (type) {
182            case Sensor.TYPE_ACCELEROMETER:
183                if (mDeviceMotionIsActive) {
184                    gotAccelerationIncludingGravity(values[0], values[1], values[2]);
185                }
186                break;
187            case Sensor.TYPE_LINEAR_ACCELERATION:
188                if (mDeviceMotionIsActive) {
189                    gotAcceleration(values[0], values[1], values[2]);
190                }
191                break;
192            case Sensor.TYPE_GYROSCOPE:
193                if (mDeviceMotionIsActive) {
194                    gotRotationRate(values[0], values[1], values[2]);
195                }
196                break;
197            case Sensor.TYPE_ROTATION_VECTOR:
198                if (mDeviceOrientationIsActive) {
199                    if (values.length > 4) {
200                        // On some Samsung devices SensorManager.getRotationMatrixFromVector
201                        // appears to throw an exception if rotation vector has length > 4.
202                        // For the purposes of this class the first 4 values of the
203                        // rotation vector are sufficient (see crbug.com/335298 for details).
204                        if (mTruncatedRotationVector == null) {
205                            mTruncatedRotationVector = new float[4];
206                        }
207                        System.arraycopy(values, 0, mTruncatedRotationVector, 0, 4);
208                        getOrientationFromRotationVector(mTruncatedRotationVector);
209                    } else {
210                        getOrientationFromRotationVector(values);
211                    }
212                }
213                break;
214            default:
215                // Unexpected
216                return;
217        }
218    }
219
220    /**
221     * Returns orientation angles from a rotation matrix, such that the angles are according
222     * to spec {@link http://dev.w3.org/geo/api/spec-source-orientation.html}.
223     * <p>
224     * It is assumed the rotation matrix transforms a 3D column vector from device coordinate system
225     * to the world's coordinate system, as e.g. computed by {@see SensorManager.getRotationMatrix}.
226     * <p>
227     * In particular we compute the decomposition of a given rotation matrix R such that <br>
228     * R = Rz(alpha) * Rx(beta) * Ry(gamma), <br>
229     * where Rz, Rx and Ry are rotation matrices around Z, X and Y axes in the world coordinate
230     * reference frame respectively. The reference frame consists of three orthogonal axes X, Y, Z
231     * where X points East, Y points north and Z points upwards perpendicular to the ground plane.
232     * The computed angles alpha, beta and gamma are in radians and clockwise-positive when viewed
233     * along the positive direction of the corresponding axis. Except for the special case when the
234     * beta angle is +-pi/2 these angles uniquely define the orientation of a mobile device in 3D
235     * space. The alpha-beta-gamma representation resembles the yaw-pitch-roll convention used in
236     * vehicle dynamics, however it does not exactly match it. One of the differences is that the
237     * 'pitch' angle beta is allowed to be within [-pi, pi). A mobile device with pitch angle
238     * greater than pi/2 could correspond to a user lying down and looking upward at the screen.
239     *
240     * <p>
241     * Upon return the array values is filled with the result,
242     * <ul>
243     * <li>values[0]: rotation around the Z axis, alpha in [0, 2*pi)</li>
244     * <li>values[1]: rotation around the X axis, beta in [-pi, pi)</li>
245     * <li>values[2]: rotation around the Y axis, gamma in [-pi/2, pi/2)</li>
246     * </ul>
247     * <p>
248     *
249     * @param R
250     *        a 3x3 rotation matrix {@see SensorManager.getRotationMatrix}.
251     *
252     * @param values
253     *        an array of 3 doubles to hold the result.
254     *
255     * @return the array values passed as argument.
256     */
257    @VisibleForTesting
258    public static double[] computeDeviceOrientationFromRotationMatrix(float[] R, double[] values) {
259        /*
260         * 3x3 (length=9) case:
261         *   /  R[ 0]   R[ 1]   R[ 2]  \
262         *   |  R[ 3]   R[ 4]   R[ 5]  |
263         *   \  R[ 6]   R[ 7]   R[ 8]  /
264         *
265         */
266        if (R.length != 9)
267            return values;
268
269        if (R[8] > 0) {  // cos(beta) > 0
270            values[0] = Math.atan2(-R[1], R[4]);
271            values[1] = Math.asin(R[7]);           // beta (-pi/2, pi/2)
272            values[2] = Math.atan2(-R[6], R[8]);   // gamma (-pi/2, pi/2)
273        } else if (R[8] < 0) {  // cos(beta) < 0
274            values[0] = Math.atan2(R[1], -R[4]);
275            values[1] = -Math.asin(R[7]);
276            values[1] += (values[1] >= 0) ? -Math.PI : Math.PI; // beta [-pi,-pi/2) U (pi/2,pi)
277            values[2] = Math.atan2(R[6], -R[8]);   // gamma (-pi/2, pi/2)
278        } else { // R[8] == 0
279            if (R[6] > 0) {  // cos(gamma) == 0, cos(beta) > 0
280                values[0] = Math.atan2(-R[1], R[4]);
281                values[1] = Math.asin(R[7]);       // beta [-pi/2, pi/2]
282                values[2] = -Math.PI / 2;          // gamma = -pi/2
283            } else if (R[6] < 0) { // cos(gamma) == 0, cos(beta) < 0
284                values[0] = Math.atan2(R[1], -R[4]);
285                values[1] = -Math.asin(R[7]);
286                values[1] += (values[1] >= 0) ? -Math.PI : Math.PI; // beta [-pi,-pi/2) U (pi/2,pi)
287                values[2] = -Math.PI / 2;          // gamma = -pi/2
288            } else { // R[6] == 0, cos(beta) == 0
289                // gimbal lock discontinuity
290                values[0] = Math.atan2(R[3], R[0]);
291                values[1] = (R[7] > 0) ? Math.PI / 2 : -Math.PI / 2;  // beta = +-pi/2
292                values[2] = 0;                                        // gamma = 0
293            }
294        }
295
296        // alpha is in [-pi, pi], make sure it is in [0, 2*pi).
297        if (values[0] < 0)
298            values[0] += 2 * Math.PI; // alpha [0, 2*pi)
299
300        return values;
301    }
302
303    private void getOrientationFromRotationVector(float[] rotationVector) {
304        float[] deviceRotationMatrix = new float[9];
305        SensorManager.getRotationMatrixFromVector(deviceRotationMatrix, rotationVector);
306
307        double[] rotationAngles = new double[3];
308        computeDeviceOrientationFromRotationMatrix(deviceRotationMatrix, rotationAngles);
309
310        gotOrientation(Math.toDegrees(rotationAngles[0]),
311                       Math.toDegrees(rotationAngles[1]),
312                       Math.toDegrees(rotationAngles[2]));
313    }
314
315    private SensorManagerProxy getSensorManagerProxy() {
316        if (mSensorManagerProxy != null) {
317            return mSensorManagerProxy;
318        }
319
320        SensorManager sensorManager = ThreadUtils.runOnUiThreadBlockingNoException(
321                new Callable<SensorManager>() {
322            @Override
323            public SensorManager call() {
324                return (SensorManager) mAppContext.getSystemService(Context.SENSOR_SERVICE);
325            }
326        });
327
328        if (sensorManager != null) {
329            mSensorManagerProxy = new SensorManagerProxyImpl(sensorManager);
330        }
331        return mSensorManagerProxy;
332    }
333
334    @VisibleForTesting
335    void setSensorManagerProxy(SensorManagerProxy sensorManagerProxy) {
336        mSensorManagerProxy = sensorManagerProxy;
337    }
338
339    private void setEventTypeActive(int eventType, boolean value) {
340        switch (eventType) {
341            case DEVICE_ORIENTATION:
342                mDeviceOrientationIsActive = value;
343                return;
344            case DEVICE_MOTION:
345                mDeviceMotionIsActive = value;
346                return;
347        }
348    }
349
350    /**
351     * @param sensorTypes List of sensors to activate.
352     * @param rateInMilliseconds Intended delay (in milliseconds) between sensor readings.
353     * @param failOnMissingSensor If true the method returns true only if all sensors could be
354     *                            activated. When false the method return true if at least one
355     *                            sensor in sensorTypes could be activated.
356     */
357    private boolean registerSensors(Set<Integer> sensorTypes, int rateInMilliseconds,
358            boolean failOnMissingSensor) {
359        Set<Integer> sensorsToActivate = new HashSet<Integer>(sensorTypes);
360        sensorsToActivate.removeAll(mActiveSensors);
361        boolean success = false;
362
363        for (Integer sensorType : sensorsToActivate) {
364            boolean result = registerForSensorType(sensorType, rateInMilliseconds);
365            if (!result && failOnMissingSensor) {
366                // restore the previous state upon failure
367                unregisterSensors(sensorsToActivate);
368                return false;
369            }
370            if (result) {
371                mActiveSensors.add(sensorType);
372                success = true;
373            }
374        }
375        return success;
376    }
377
378    private void unregisterSensors(Iterable<Integer> sensorTypes) {
379        for (Integer sensorType : sensorTypes) {
380            if (mActiveSensors.contains(sensorType)) {
381                getSensorManagerProxy().unregisterListener(this, sensorType);
382                mActiveSensors.remove(sensorType);
383            }
384        }
385    }
386
387    private boolean registerForSensorType(int type, int rateInMilliseconds) {
388        SensorManagerProxy sensorManager = getSensorManagerProxy();
389        if (sensorManager == null) {
390            return false;
391        }
392        final int rateInMicroseconds = 1000 * rateInMilliseconds;
393        return sensorManager.registerListener(this, type, rateInMicroseconds, getHandler());
394    }
395
396    protected void gotOrientation(double alpha, double beta, double gamma) {
397        synchronized (mNativePtrLock) {
398            if (mNativePtr != 0) {
399                nativeGotOrientation(mNativePtr, alpha, beta, gamma);
400            }
401        }
402    }
403
404    protected void gotAcceleration(double x, double y, double z) {
405        synchronized (mNativePtrLock) {
406            if (mNativePtr != 0) {
407                nativeGotAcceleration(mNativePtr, x, y, z);
408            }
409        }
410    }
411
412    protected void gotAccelerationIncludingGravity(double x, double y, double z) {
413        synchronized (mNativePtrLock) {
414            if (mNativePtr != 0) {
415                nativeGotAccelerationIncludingGravity(mNativePtr, x, y, z);
416            }
417        }
418    }
419
420    protected void gotRotationRate(double alpha, double beta, double gamma) {
421        synchronized (mNativePtrLock) {
422            if (mNativePtr != 0) {
423                nativeGotRotationRate(mNativePtr, alpha, beta, gamma);
424            }
425        }
426    }
427
428    private Handler getHandler() {
429        // TODO(timvolodine): Remove the mHandlerLock when sure that getHandler is not called
430        // from multiple threads. This will be the case when device motion and device orientation
431        // use the same polling thread (also see crbug/234282).
432        synchronized (mHandlerLock) {
433            if (mHandler == null) {
434                HandlerThread thread = new HandlerThread("DeviceMotionAndOrientation");
435                thread.start();
436                mHandler = new Handler(thread.getLooper());  // blocks on thread start
437            }
438            return mHandler;
439        }
440    }
441
442    @CalledByNative
443    static DeviceSensors getInstance(Context appContext) {
444        synchronized (sSingletonLock) {
445            if (sSingleton == null) {
446                sSingleton = new DeviceSensors(appContext);
447            }
448            return sSingleton;
449        }
450    }
451
452    /**
453     * Native JNI calls,
454     * see content/browser/device_sensors/sensor_manager_android.cc
455     */
456
457    /**
458     * Orientation of the device with respect to its reference frame.
459     */
460    private native void nativeGotOrientation(
461            long nativeSensorManagerAndroid,
462            double alpha, double beta, double gamma);
463
464    /**
465     * Linear acceleration without gravity of the device with respect to its body frame.
466     */
467    private native void nativeGotAcceleration(
468            long nativeSensorManagerAndroid,
469            double x, double y, double z);
470
471    /**
472     * Acceleration including gravity of the device with respect to its body frame.
473     */
474    private native void nativeGotAccelerationIncludingGravity(
475            long nativeSensorManagerAndroid,
476            double x, double y, double z);
477
478    /**
479     * Rotation rate of the device with respect to its body frame.
480     */
481    private native void nativeGotRotationRate(
482            long nativeSensorManagerAndroid,
483            double alpha, double beta, double gamma);
484
485    /**
486     * Need the an interface for SensorManager for testing.
487     */
488    interface SensorManagerProxy {
489        public boolean registerListener(SensorEventListener listener, int sensorType, int rate,
490                Handler handler);
491        public void unregisterListener(SensorEventListener listener, int sensorType);
492    }
493
494    static class SensorManagerProxyImpl implements SensorManagerProxy {
495        private final SensorManager mSensorManager;
496
497        SensorManagerProxyImpl(SensorManager sensorManager) {
498            mSensorManager = sensorManager;
499        }
500
501        @Override
502        public boolean registerListener(SensorEventListener listener, int sensorType, int rate,
503                Handler handler) {
504            List<Sensor> sensors = mSensorManager.getSensorList(sensorType);
505            if (sensors.isEmpty()) {
506                return false;
507            }
508            return mSensorManager.registerListener(listener, sensors.get(0), rate, handler);
509        }
510
511        @Override
512        public void unregisterListener(SensorEventListener listener, int sensorType) {
513            List<Sensor> sensors = mSensorManager.getSensorList(sensorType);
514            if (!sensors.isEmpty()) {
515                mSensorManager.unregisterListener(listener, sensors.get(0));
516            }
517        }
518    }
519
520}
521