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