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.
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.ThreadUtils;
23 import java.util.HashSet;
24 import java.util.List;
26 import java.util.concurrent.Callable;
29 * Android implementation of the device motion and orientation APIs.
31 @JNINamespace("content")
32 class DeviceSensors implements SensorEventListener {
34 private static final String TAG = "DeviceMotionAndOrientation";
36 // These fields are lazily initialized by getHandler().
37 private Thread mThread;
38 private Handler mHandler;
40 // A reference to the application context in order to acquire the SensorService.
41 private final Context mAppContext;
43 // The lock to access the mHandler.
44 private final Object mHandlerLock = new Object();
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;
50 // The lock to access the mNativePtr.
51 private final Object mNativePtrLock = new Object();
53 // Holds a shortened version of the rotation vector for compatibility purposes.
54 private float[] mTruncatedRotationVector;
56 // Lazily initialized when registering for notifications.
57 private SensorManagerProxy mSensorManagerProxy;
59 // The only instance of that class and its associated lock.
60 private static DeviceSensors sSingleton;
61 private static Object sSingletonLock = new Object();
64 * constants for using in JNI calls, also see
65 * content/browser/device_sensors/sensor_manager_android.cc
67 static final int DEVICE_ORIENTATION = 0;
68 static final int DEVICE_MOTION = 1;
70 static final Set<Integer> DEVICE_ORIENTATION_SENSORS = CollectionUtil.newHashSet(
71 Sensor.TYPE_ROTATION_VECTOR);
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 DeviceSensors(Context context) {
84 mAppContext = context.getApplicationContext();
88 * Start listening for sensor events. If this object is already listening
89 * for events, the old callback is unregistered first.
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
96 * @return True on success.
99 public boolean start(long nativePtr, int eventType, int rateInMilliseconds) {
100 boolean success = false;
101 synchronized (mNativePtrLock) {
103 case DEVICE_ORIENTATION:
104 success = registerSensors(DEVICE_ORIENTATION_SENSORS, rateInMilliseconds,
108 // note: device motion spec does not require all sensors to be available
109 success = registerSensors(DEVICE_MOTION_SENSORS, rateInMilliseconds, false);
112 Log.e(TAG, "Unknown event type: " + eventType);
116 mNativePtr = nativePtr;
117 setEventTypeActive(eventType, true);
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();
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.
134 * @param eventType Type of event to listen to, can be either DEVICE_ORIENTATION or
136 * We strictly guarantee that the corresponding native*() methods will not be called
137 * after this method returns.
140 public void stop(int eventType) {
141 Set<Integer> sensorsToRemainActive = new HashSet<Integer>();
142 synchronized (mNativePtrLock) {
144 case DEVICE_ORIENTATION:
145 if (mDeviceMotionIsActive) {
146 sensorsToRemainActive.addAll(DEVICE_MOTION_SENSORS);
150 if (mDeviceOrientationIsActive) {
151 sensorsToRemainActive.addAll(DEVICE_ORIENTATION_SENSORS);
155 Log.e(TAG, "Unknown event type: " + eventType);
159 Set<Integer> sensorsToDeactivate = new HashSet<Integer>(mActiveSensors);
160 sensorsToDeactivate.removeAll(sensorsToRemainActive);
161 unregisterSensors(sensorsToDeactivate);
162 setEventTypeActive(eventType, false);
163 if (mActiveSensors.isEmpty()) {
170 public void onAccuracyChanged(Sensor sensor, int accuracy) {
175 public void onSensorChanged(SensorEvent event) {
176 sensorChanged(event.sensor.getType(), event.values);
180 void sensorChanged(int type, float[] values) {
182 case Sensor.TYPE_ACCELEROMETER:
183 if (mDeviceMotionIsActive) {
184 gotAccelerationIncludingGravity(values[0], values[1], values[2]);
187 case Sensor.TYPE_LINEAR_ACCELERATION:
188 if (mDeviceMotionIsActive) {
189 gotAcceleration(values[0], values[1], values[2]);
192 case Sensor.TYPE_GYROSCOPE:
193 if (mDeviceMotionIsActive) {
194 gotRotationRate(values[0], values[1], values[2]);
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];
207 System.arraycopy(values, 0, mTruncatedRotationVector, 0, 4);
208 getOrientationFromRotationVector(mTruncatedRotationVector);
210 getOrientationFromRotationVector(values);
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}.
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}.
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.
241 * Upon return the array values is filled with the result,
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>
250 * a 3x3 rotation matrix {@see SensorManager.getRotationMatrix}.
253 * an array of 3 doubles to hold the result.
255 * @return the array values passed as argument.
258 public static double[] computeDeviceOrientationFromRotationMatrix(float[] R, double[] values) {
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] /
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
296 // alpha is in [-pi, pi], make sure it is in [0, 2*pi).
298 values[0] += 2 * Math.PI; // alpha [0, 2*pi)
303 private void getOrientationFromRotationVector(float[] rotationVector) {
304 float[] deviceRotationMatrix = new float[9];
305 SensorManager.getRotationMatrixFromVector(deviceRotationMatrix, rotationVector);
307 double[] rotationAngles = new double[3];
308 computeDeviceOrientationFromRotationMatrix(deviceRotationMatrix, rotationAngles);
310 gotOrientation(Math.toDegrees(rotationAngles[0]),
311 Math.toDegrees(rotationAngles[1]),
312 Math.toDegrees(rotationAngles[2]));
315 private SensorManagerProxy getSensorManagerProxy() {
316 if (mSensorManagerProxy != null) {
317 return mSensorManagerProxy;
320 SensorManager sensorManager = ThreadUtils.runOnUiThreadBlockingNoException(
321 new Callable<SensorManager>() {
323 public SensorManager call() {
324 return (SensorManager) mAppContext.getSystemService(Context.SENSOR_SERVICE);
328 if (sensorManager != null) {
329 mSensorManagerProxy = new SensorManagerProxyImpl(sensorManager);
331 return mSensorManagerProxy;
335 void setSensorManagerProxy(SensorManagerProxy sensorManagerProxy) {
336 mSensorManagerProxy = sensorManagerProxy;
339 private void setEventTypeActive(int eventType, boolean value) {
341 case DEVICE_ORIENTATION:
342 mDeviceOrientationIsActive = value;
345 mDeviceMotionIsActive = value;
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.
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;
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);
371 mActiveSensors.add(sensorType);
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);
387 private boolean registerForSensorType(int type, int rateInMilliseconds) {
388 SensorManagerProxy sensorManager = getSensorManagerProxy();
389 if (sensorManager == null) {
392 final int rateInMicroseconds = 1000 * rateInMilliseconds;
393 return sensorManager.registerListener(this, type, rateInMicroseconds, getHandler());
396 protected void gotOrientation(double alpha, double beta, double gamma) {
397 synchronized (mNativePtrLock) {
398 if (mNativePtr != 0) {
399 nativeGotOrientation(mNativePtr, alpha, beta, gamma);
404 protected void gotAcceleration(double x, double y, double z) {
405 synchronized (mNativePtrLock) {
406 if (mNativePtr != 0) {
407 nativeGotAcceleration(mNativePtr, x, y, z);
412 protected void gotAccelerationIncludingGravity(double x, double y, double z) {
413 synchronized (mNativePtrLock) {
414 if (mNativePtr != 0) {
415 nativeGotAccelerationIncludingGravity(mNativePtr, x, y, z);
420 protected void gotRotationRate(double alpha, double beta, double gamma) {
421 synchronized (mNativePtrLock) {
422 if (mNativePtr != 0) {
423 nativeGotRotationRate(mNativePtr, alpha, beta, gamma);
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");
436 mHandler = new Handler(thread.getLooper()); // blocks on thread start
443 static DeviceSensors getInstance(Context appContext) {
444 synchronized (sSingletonLock) {
445 if (sSingleton == null) {
446 sSingleton = new DeviceSensors(appContext);
454 * see content/browser/device_sensors/sensor_manager_android.cc
458 * Orientation of the device with respect to its reference frame.
460 private native void nativeGotOrientation(
461 long nativeSensorManagerAndroid,
462 double alpha, double beta, double gamma);
465 * Linear acceleration without gravity of the device with respect to its body frame.
467 private native void nativeGotAcceleration(
468 long nativeSensorManagerAndroid,
469 double x, double y, double z);
472 * Acceleration including gravity of the device with respect to its body frame.
474 private native void nativeGotAccelerationIncludingGravity(
475 long nativeSensorManagerAndroid,
476 double x, double y, double z);
479 * Rotation rate of the device with respect to its body frame.
481 private native void nativeGotRotationRate(
482 long nativeSensorManagerAndroid,
483 double alpha, double beta, double gamma);
486 * Need the an interface for SensorManager for testing.
488 interface SensorManagerProxy {
489 public boolean registerListener(SensorEventListener listener, int sensorType, int rate,
491 public void unregisterListener(SensorEventListener listener, int sensorType);
494 static class SensorManagerProxyImpl implements SensorManagerProxy {
495 private final SensorManager mSensorManager;
497 SensorManagerProxyImpl(SensorManager sensorManager) {
498 mSensorManager = sensorManager;
502 public boolean registerListener(SensorEventListener listener, int sensorType, int rate,
504 List<Sensor> sensors = mSensorManager.getSensorList(sensorType);
505 if (sensors.isEmpty()) {
508 return mSensorManager.registerListener(listener, sensors.get(0), rate, handler);
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));