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.
5 package org.chromium.content.browser;
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;
16 import com.google.common.annotations.VisibleForTesting;
18 import org.chromium.base.CalledByNative;
19 import org.chromium.base.CollectionUtil;
20 import org.chromium.base.JNINamespace;
21 import org.chromium.base.WeakContext;
23 import java.util.HashSet;
24 import java.util.List;
28 * Android implementation of the device motion and orientation APIs.
30 @JNINamespace("content")
31 class DeviceMotionAndOrientation implements SensorEventListener {
33 private static final String TAG = "DeviceMotionAndOrientation";
35 // These fields are lazily initialized by getHandler().
36 private Thread mThread;
37 private Handler mHandler;
39 // The lock to access the mHandler.
40 private final Object mHandlerLock = new Object();
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;
46 // The lock to access the mNativePtr.
47 private final Object mNativePtrLock = new Object();
49 // The acceleration vector including gravity expressed in the body frame.
50 private float[] mAccelerationIncludingGravityVector;
52 // The geomagnetic vector expressed in the body frame.
53 private float[] mMagneticFieldVector;
55 // Lazily initialized when registering for notifications.
56 private SensorManagerProxy mSensorManagerProxy;
58 // The only instance of that class and its associated lock.
59 private static DeviceMotionAndOrientation sSingleton;
60 private static Object sSingletonLock = new Object();
63 * constants for using in JNI calls, also see
64 * content/browser/device_orientation/data_fetcher_impl_android.cc
66 static final int DEVICE_ORIENTATION = 0;
67 static final int DEVICE_MOTION = 1;
69 static final Set<Integer> DEVICE_ORIENTATION_SENSORS = CollectionUtil.newHashSet(
70 Sensor.TYPE_ACCELEROMETER,
71 Sensor.TYPE_MAGNETIC_FIELD);
73 static final Set<Integer> DEVICE_MOTION_SENSORS = CollectionUtil.newHashSet(
74 Sensor.TYPE_ACCELEROMETER,
75 Sensor.TYPE_LINEAR_ACCELERATION,
76 Sensor.TYPE_GYROSCOPE);
79 final Set<Integer> mActiveSensors = new HashSet<Integer>();
80 boolean mDeviceMotionIsActive = false;
81 boolean mDeviceOrientationIsActive = false;
83 protected DeviceMotionAndOrientation() {
87 * Start listening for sensor events. If this object is already listening
88 * for events, the old callback is unregistered first.
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
95 * @return True on success.
98 public boolean start(int nativePtr, int eventType, int rateInMilliseconds) {
99 boolean success = false;
100 synchronized (mNativePtrLock) {
102 case DEVICE_ORIENTATION:
103 success = registerSensors(DEVICE_ORIENTATION_SENSORS, rateInMilliseconds,
107 // note: device motion spec does not require all sensors to be available
108 success = registerSensors(DEVICE_MOTION_SENSORS, rateInMilliseconds, false);
111 Log.e(TAG, "Unknown event type: " + eventType);
115 mNativePtr = nativePtr;
116 setEventTypeActive(eventType, true);
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();
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.
133 * @param eventType Type of event to listen to, can be either DEVICE_ORIENTATION or
135 * We strictly guarantee that the corresponding native*() methods will not be called
136 * after this method returns.
139 public void stop(int eventType) {
140 Set<Integer> sensorsToRemainActive = new HashSet<Integer>();
141 synchronized (mNativePtrLock) {
143 case DEVICE_ORIENTATION:
144 if (mDeviceMotionIsActive) {
145 sensorsToRemainActive.addAll(DEVICE_MOTION_SENSORS);
149 if (mDeviceOrientationIsActive) {
150 sensorsToRemainActive.addAll(DEVICE_ORIENTATION_SENSORS);
154 Log.e(TAG, "Unknown event type: " + eventType);
158 Set<Integer> sensorsToDeactivate = new HashSet<Integer>(mActiveSensors);
159 sensorsToDeactivate.removeAll(sensorsToRemainActive);
160 unregisterSensors(sensorsToDeactivate);
161 setEventTypeActive(eventType, false);
162 if (mActiveSensors.isEmpty()) {
169 public void onAccuracyChanged(Sensor sensor, int accuracy) {
174 public void onSensorChanged(SensorEvent event) {
175 sensorChanged(event.sensor.getType(), event.values);
179 void sensorChanged(int type, float[] values) {
182 case Sensor.TYPE_ACCELEROMETER:
183 if (mAccelerationIncludingGravityVector == null) {
184 mAccelerationIncludingGravityVector = new float[3];
186 System.arraycopy(values, 0, mAccelerationIncludingGravityVector,
187 0, mAccelerationIncludingGravityVector.length);
188 if (mDeviceMotionIsActive) {
189 gotAccelerationIncludingGravity(
190 mAccelerationIncludingGravityVector[0],
191 mAccelerationIncludingGravityVector[1],
192 mAccelerationIncludingGravityVector[2]);
194 if (mDeviceOrientationIsActive) {
195 getOrientationUsingGetRotationMatrix();
198 case Sensor.TYPE_LINEAR_ACCELERATION:
199 if (mDeviceMotionIsActive) {
200 gotAcceleration(values[0], values[1], values[2]);
203 case Sensor.TYPE_GYROSCOPE:
204 if (mDeviceMotionIsActive) {
205 gotRotationRate(values[0], values[1], values[2]);
208 case Sensor.TYPE_MAGNETIC_FIELD:
209 if (mMagneticFieldVector == null) {
210 mMagneticFieldVector = new float[3];
212 System.arraycopy(values, 0, mMagneticFieldVector, 0, mMagneticFieldVector.length);
213 if (mDeviceOrientationIsActive) {
214 getOrientationUsingGetRotationMatrix();
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}.
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}.
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.
244 * Upon return the array values is filled with the result,
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>
253 * a 3x3 rotation matrix {@see SensorManager.getRotationMatrix}.
256 * an array of 3 doubles to hold the result.
258 * @return the array values passed as argument.
261 public static double[] computeDeviceOrientationFromRotationMatrix(float[] R, double[] values) {
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] /
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
299 // alpha is in [-pi, pi], make sure it is in [0, 2*pi).
301 values[0] += 2 * Math.PI; // alpha [0, 2*pi)
306 private void getOrientationUsingGetRotationMatrix() {
307 if (mAccelerationIncludingGravityVector == null || mMagneticFieldVector == null) {
311 float[] deviceRotationMatrix = new float[9];
312 if (!SensorManager.getRotationMatrix(deviceRotationMatrix, null,
313 mAccelerationIncludingGravityVector, mMagneticFieldVector)) {
317 double[] rotationAngles = new double[3];
318 computeDeviceOrientationFromRotationMatrix(deviceRotationMatrix, rotationAngles);
320 gotOrientation(Math.toDegrees(rotationAngles[0]),
321 Math.toDegrees(rotationAngles[1]),
322 Math.toDegrees(rotationAngles[2]));
325 private SensorManagerProxy getSensorManagerProxy() {
326 if (mSensorManagerProxy != null) {
327 return mSensorManagerProxy;
329 SensorManager sensorManager = (SensorManager)WeakContext.getSystemService(
330 Context.SENSOR_SERVICE);
331 if (sensorManager != null) {
332 mSensorManagerProxy = new SensorManagerProxyImpl(sensorManager);
334 return mSensorManagerProxy;
338 void setSensorManagerProxy(SensorManagerProxy sensorManagerProxy) {
339 mSensorManagerProxy = sensorManagerProxy;
342 private void setEventTypeActive(int eventType, boolean value) {
344 case DEVICE_ORIENTATION:
345 mDeviceOrientationIsActive = value;
348 mDeviceMotionIsActive = value;
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.
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;
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);
374 mActiveSensors.add(sensorType);
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);
390 private boolean registerForSensorType(int type, int rateInMilliseconds) {
391 SensorManagerProxy sensorManager = getSensorManagerProxy();
392 if (sensorManager == null) {
395 final int rateInMicroseconds = 1000 * rateInMilliseconds;
396 return sensorManager.registerListener(this, type, rateInMicroseconds, getHandler());
399 protected void gotOrientation(double alpha, double beta, double gamma) {
400 synchronized (mNativePtrLock) {
401 if (mNativePtr != 0) {
402 nativeGotOrientation(mNativePtr, alpha, beta, gamma);
407 protected void gotAcceleration(double x, double y, double z) {
408 synchronized (mNativePtrLock) {
409 if (mNativePtr != 0) {
410 nativeGotAcceleration(mNativePtr, x, y, z);
415 protected void gotAccelerationIncludingGravity(double x, double y, double z) {
416 synchronized (mNativePtrLock) {
417 if (mNativePtr != 0) {
418 nativeGotAccelerationIncludingGravity(mNativePtr, x, y, z);
423 protected void gotRotationRate(double alpha, double beta, double gamma) {
424 synchronized (mNativePtrLock) {
425 if (mNativePtr != 0) {
426 nativeGotRotationRate(mNativePtr, alpha, beta, gamma);
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");
439 mHandler = new Handler(thread.getLooper()); // blocks on thread start
446 static DeviceMotionAndOrientation getInstance() {
447 synchronized (sSingletonLock) {
448 if (sSingleton == null) {
449 sSingleton = new DeviceMotionAndOrientation();
457 * see content/browser/device_orientation/data_fetcher_impl_android.cc
461 * Orientation of the device with respect to its reference frame.
463 private native void nativeGotOrientation(
464 int nativeDataFetcherImplAndroid,
465 double alpha, double beta, double gamma);
468 * Linear acceleration without gravity of the device with respect to its body frame.
470 private native void nativeGotAcceleration(
471 int nativeDataFetcherImplAndroid,
472 double x, double y, double z);
475 * Acceleration including gravity of the device with respect to its body frame.
477 private native void nativeGotAccelerationIncludingGravity(
478 int nativeDataFetcherImplAndroid,
479 double x, double y, double z);
482 * Rotation rate of the device with respect to its body frame.
484 private native void nativeGotRotationRate(
485 int nativeDataFetcherImplAndroid,
486 double alpha, double beta, double gamma);
489 * Need the an interface for SensorManager for testing.
491 interface SensorManagerProxy {
492 public boolean registerListener(SensorEventListener listener, int sensorType, int rate,
494 public void unregisterListener(SensorEventListener listener, int sensorType);
497 static class SensorManagerProxyImpl implements SensorManagerProxy {
498 private final SensorManager mSensorManager;
500 SensorManagerProxyImpl(SensorManager sensorManager) {
501 mSensorManager = sensorManager;
505 public boolean registerListener(SensorEventListener listener, int sensorType, int rate,
507 List<Sensor> sensors = mSensorManager.getSensorList(sensorType);
508 if (sensors.isEmpty()) {
511 return mSensorManager.registerListener(listener, sensors.get(0), rate, handler);
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));