Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / DeviceSensors.java
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
5 package org.chromium.content.browser;
6
7 import android.content.Context;
8 import android.hardware.Sensor;
9 import android.hardware.SensorEvent;
10 import android.hardware.SensorEventListener;
11 import android.hardware.SensorManager;
12 import android.os.Handler;
13 import android.os.HandlerThread;
14 import android.util.Log;
15
16 import com.google.common.annotations.VisibleForTesting;
17
18 import org.chromium.base.CalledByNative;
19 import org.chromium.base.CollectionUtil;
20 import org.chromium.base.JNINamespace;
21 import org.chromium.base.ThreadUtils;
22
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Set;
26 import java.util.concurrent.Callable;
27
28 /**
29  * Android implementation of the device motion and orientation APIs.
30  */
31 @JNINamespace("content")
32 class 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 }