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