Index: content/public/android/java/src/org/chromium/content/browser/DeviceMotionAndOrientation.java |
diff --git a/content/public/android/java/src/org/chromium/content/browser/DeviceOrientation.java b/content/public/android/java/src/org/chromium/content/browser/DeviceMotionAndOrientation.java |
similarity index 35% |
rename from content/public/android/java/src/org/chromium/content/browser/DeviceOrientation.java |
rename to content/public/android/java/src/org/chromium/content/browser/DeviceMotionAndOrientation.java |
index a77cd28b27ab6901f6a12f7a2e4e012b3e7c8f71..866884882fa6e197b0ec75c8a91735ae36f1dbc5 100644 |
--- a/content/public/android/java/src/org/chromium/content/browser/DeviceOrientation.java |
+++ b/content/public/android/java/src/org/chromium/content/browser/DeviceMotionAndOrientation.java |
@@ -1,4 +1,4 @@ |
-// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Copyright (c) 2013 The Chromium Authors. All rights reserved. |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
@@ -11,18 +11,26 @@ import android.hardware.SensorEventListener; |
import android.hardware.SensorManager; |
import android.os.Handler; |
import android.os.Looper; |
+import android.util.Log; |
+ |
+import com.google.common.annotations.VisibleForTesting; |
+import com.google.common.collect.ImmutableSet; |
+import com.google.common.collect.Sets; |
import org.chromium.base.CalledByNative; |
import org.chromium.base.JNINamespace; |
import org.chromium.base.WeakContext; |
import java.util.List; |
+import java.util.Set; |
/** |
- * Android implementation of the DeviceOrientation API. |
+ * Android implementation of the device motion and orientation APIs. |
*/ |
@JNINamespace("content") |
-class DeviceOrientation implements SensorEventListener { |
+class DeviceMotionAndOrientation implements SensorEventListener { |
+ |
+ private static final String TAG = "DeviceMotionAndOrientation"; |
// These fields are lazily initialized by getHandler(). |
private Thread mThread; |
@@ -38,20 +46,41 @@ class DeviceOrientation implements SensorEventListener { |
// The lock to access the mNativePtr. |
private Object mNativePtrLock = new Object(); |
- // The gravity vector expressed in the body frame. |
- private float[] mGravityVector; |
+ // The acceleration vector including gravity expressed in the body frame. |
+ private float[] mAccelerationVector; |
// The geomagnetic vector expressed in the body frame. |
private float[] mMagneticFieldVector; |
// Lazily initialized when registering for notifications. |
- private SensorManager mSensorManager; |
+ private SensorManagerProxy mSensorManagerProxy; |
// The only instance of that class and its associated lock. |
- private static DeviceOrientation sSingleton; |
+ private static DeviceMotionAndOrientation sSingleton; |
private static Object sSingletonLock = new Object(); |
- private DeviceOrientation() { |
+ /** |
+ * constants for using in JNI calls, also see |
+ * content/browser/device_orientation/data_fetcher_impl_android.cc |
+ */ |
+ static final int DEVICE_ORIENTATION = 0; |
+ static final int DEVICE_MOTION = 1; |
+ |
+ static final ImmutableSet<Integer> DEVICE_ORIENTATION_SENSORS = ImmutableSet.of( |
+ Sensor.TYPE_ACCELEROMETER, |
+ Sensor.TYPE_MAGNETIC_FIELD); |
+ |
+ static final ImmutableSet<Integer> DEVICE_MOTION_SENSORS = ImmutableSet.of( |
+ Sensor.TYPE_ACCELEROMETER, |
+ Sensor.TYPE_LINEAR_ACCELERATION, |
+ Sensor.TYPE_GYROSCOPE); |
+ |
+ @VisibleForTesting |
+ final Set<Integer> mActiveSensors = Sets.newHashSet(); |
+ boolean mDeviceMotionIsActive = false; |
+ boolean mDeviceOrientationIsActive = false; |
+ |
+ protected DeviceMotionAndOrientation() { |
} |
/** |
@@ -61,32 +90,70 @@ class DeviceOrientation implements SensorEventListener { |
* @param nativePtr Value to pass to nativeGotOrientation() for each event. |
* @param rateInMilliseconds Requested callback rate in milliseconds. The |
* actual rate may be higher. Unwanted events should be ignored. |
+ * @param eventType Type of event to listen to, can be either DEVICE_ORIENTATION or |
+ * DEVICE_MOTION. |
* @return True on success. |
*/ |
@CalledByNative |
- public boolean start(int nativePtr, int rateInMilliseconds) { |
+ public boolean start(int nativePtr, int eventType, int rateInMilliseconds) { |
+ boolean success = false; |
synchronized (mNativePtrLock) { |
- stop(); |
- if (registerForSensors(rateInMilliseconds)) { |
+ switch (eventType) { |
+ case DEVICE_ORIENTATION: |
+ success = registerSensors(DEVICE_ORIENTATION_SENSORS, rateInMilliseconds, |
+ true); |
+ break; |
+ case DEVICE_MOTION: |
+ // note: device motion spec does not require all sensors to be available |
+ success = registerSensors(DEVICE_MOTION_SENSORS, rateInMilliseconds, false); |
+ break; |
+ default: |
+ Log.e(TAG, "Unknown event type: " + eventType); |
+ return false; |
+ } |
+ if (success) { |
mNativePtr = nativePtr; |
- return true; |
+ setEventTypeActive(eventType, true); |
} |
- return false; |
+ return success; |
} |
} |
/** |
- * Stop listening for sensor events. Always succeeds. |
+ * Stop listening to sensors for a given event type. Ensures that sensors are not disabled |
+ * if they are still in use by a different event type. |
* |
- * We strictly guarantee that nativeGotOrientation() will not be called |
+ * @param eventType Type of event to listen to, can be either DEVICE_ORIENTATION or |
+ * DEVICE_MOTION. |
+ * We strictly guarantee that the corresponding native*() methods will not be called |
* after this method returns. |
*/ |
@CalledByNative |
- public void stop() { |
+ public void stop(int eventType) { |
+ Set<Integer> sensorsToRemainActive = Sets.newHashSet(); |
synchronized (mNativePtrLock) { |
- if (mNativePtr != 0) { |
+ switch (eventType) { |
+ case DEVICE_ORIENTATION: |
+ if (mDeviceMotionIsActive) { |
+ sensorsToRemainActive.addAll(DEVICE_MOTION_SENSORS); |
+ } |
+ break; |
+ case DEVICE_MOTION: |
+ if (mDeviceOrientationIsActive) { |
+ sensorsToRemainActive.addAll(DEVICE_ORIENTATION_SENSORS); |
+ } |
+ break; |
+ default: |
+ Log.e(TAG, "Unknown event type: " + eventType); |
+ return; |
+ } |
+ |
+ Set<Integer> sensorsToDeactivate = Sets.newHashSet(mActiveSensors); |
+ sensorsToDeactivate.removeAll(sensorsToRemainActive); |
+ unregisterSensors(sensorsToDeactivate); |
+ setEventTypeActive(eventType, false); |
+ if (mActiveSensors.isEmpty()) { |
mNativePtr = 0; |
- unregisterForSensors(); |
} |
} |
} |
@@ -98,32 +165,53 @@ class DeviceOrientation implements SensorEventListener { |
@Override |
public void onSensorChanged(SensorEvent event) { |
- switch (event.sensor.getType()) { |
+ sensorChanged(event.sensor.getType(), event.values); |
+ } |
+ |
+ @VisibleForTesting |
+ void sensorChanged(int type, float[] values) { |
+ |
+ switch (type) { |
case Sensor.TYPE_ACCELEROMETER: |
- if (mGravityVector == null) { |
- mGravityVector = new float[3]; |
+ if (mAccelerationVector == null) { |
+ mAccelerationVector = new float[3]; |
+ } |
+ System.arraycopy(values, 0, mAccelerationVector, 0, |
+ mAccelerationVector.length); |
+ if (mDeviceMotionIsActive) { |
+ gotAccelerationIncludingGravity(mAccelerationVector[0], mAccelerationVector[1], |
+ mAccelerationVector[2]); |
+ } |
+ break; |
+ case Sensor.TYPE_LINEAR_ACCELERATION: |
+ if (mDeviceMotionIsActive) { |
+ gotAcceleration(values[0], values[1], values[2]); |
+ } |
+ break; |
+ case Sensor.TYPE_GYROSCOPE: |
+ if (mDeviceMotionIsActive) { |
+ gotRotationRate(values[0], values[1], values[2]); |
} |
- System.arraycopy(event.values, 0, mGravityVector, 0, mGravityVector.length); |
break; |
- |
case Sensor.TYPE_MAGNETIC_FIELD: |
if (mMagneticFieldVector == null) { |
mMagneticFieldVector = new float[3]; |
} |
- System.arraycopy(event.values, 0, mMagneticFieldVector, 0, |
- mMagneticFieldVector.length); |
+ System.arraycopy(values, 0, mMagneticFieldVector, 0, |
+ mMagneticFieldVector.length); |
break; |
- |
default: |
// Unexpected |
return; |
} |
- getOrientationUsingGetRotationMatrix(); |
+ if (mDeviceOrientationIsActive) { |
+ getOrientationUsingGetRotationMatrix(); |
+ } |
} |
- void getOrientationUsingGetRotationMatrix() { |
- if (mGravityVector == null || mMagneticFieldVector == null) { |
+ private void getOrientationUsingGetRotationMatrix() { |
+ if (mAccelerationVector == null || mMagneticFieldVector == null) { |
return; |
} |
@@ -131,7 +219,7 @@ class DeviceOrientation implements SensorEventListener { |
// The rotation matrix that transforms from the body frame to the earth |
// frame. |
float[] deviceRotationMatrix = new float[9]; |
- if (!SensorManager.getRotationMatrix(deviceRotationMatrix, null, mGravityVector, |
+ if (!SensorManager.getRotationMatrix(deviceRotationMatrix, null, mAccelerationVector, |
mMagneticFieldVector)) { |
return; |
} |
@@ -163,53 +251,108 @@ class DeviceOrientation implements SensorEventListener { |
gotOrientation(alpha, beta, gamma); |
} |
- boolean registerForSensors(int rateInMilliseconds) { |
- if (registerForSensorType(Sensor.TYPE_ACCELEROMETER, rateInMilliseconds) |
- && registerForSensorType(Sensor.TYPE_MAGNETIC_FIELD, rateInMilliseconds)) { |
- return true; |
+ private SensorManagerProxy getSensorManagerProxy() { |
+ if (mSensorManagerProxy != null) { |
+ return mSensorManagerProxy; |
} |
- unregisterForSensors(); |
- return false; |
+ SensorManager sensorManager = (SensorManager)WeakContext.getSystemService( |
+ Context.SENSOR_SERVICE); |
+ if (sensorManager != null) { |
+ mSensorManagerProxy = new SensorManagerProxyImpl(sensorManager); |
+ } |
+ return mSensorManagerProxy; |
+ } |
+ |
+ @VisibleForTesting |
+ void setSensorManagerProxy(SensorManagerProxy sensorManagerProxy) { |
+ mSensorManagerProxy = sensorManagerProxy; |
} |
- private SensorManager getSensorManager() { |
- if (mSensorManager != null) { |
- return mSensorManager; |
+ private void setEventTypeActive(int eventType, boolean value) { |
+ switch (eventType) { |
+ case DEVICE_ORIENTATION: |
+ mDeviceOrientationIsActive = value; |
+ return; |
+ case DEVICE_MOTION: |
+ mDeviceMotionIsActive = value; |
+ return; |
} |
- mSensorManager = (SensorManager)WeakContext.getSystemService(Context.SENSOR_SERVICE); |
- return mSensorManager; |
} |
- void unregisterForSensors() { |
- SensorManager sensorManager = getSensorManager(); |
- if (sensorManager == null) { |
- return; |
+ /** |
+ * @param sensorTypes List of sensors to activate. |
+ * @param rateInMilliseconds Intended delay (in milliseconds) between sensor readings. |
+ * @param failOnMissingSensor If true the method returns true only if all sensors could be |
+ * activated. When false the method return true if at least one |
+ * sensor in sensorTypes could be activated. |
+ */ |
+ private boolean registerSensors(Iterable<Integer> sensorTypes, int rateInMilliseconds, |
+ boolean failOnMissingSensor) { |
+ Set<Integer> sensorsToActivate = Sets.newHashSet(sensorTypes); |
+ sensorsToActivate.removeAll(mActiveSensors); |
+ boolean success = false; |
+ |
+ for (Integer sensorType : sensorsToActivate) { |
+ boolean result = registerForSensorType(sensorType, rateInMilliseconds); |
+ if (!result && failOnMissingSensor) { |
+ // restore the previous state upon failure |
+ unregisterSensors(sensorsToActivate); |
+ return false; |
+ } |
+ if (result) { |
+ mActiveSensors.add(sensorType); |
+ success = true; |
+ } |
+ } |
+ return success; |
+ } |
+ |
+ private void unregisterSensors(Iterable<Integer> sensorTypes) { |
+ for (Integer sensorType : sensorTypes) { |
+ if (mActiveSensors.contains(sensorType)) { |
+ getSensorManagerProxy().unregisterListener(this, sensorType); |
+ mActiveSensors.remove(sensorType); |
+ } |
} |
- sensorManager.unregisterListener(this); |
} |
- boolean registerForSensorType(int type, int rateInMilliseconds) { |
- SensorManager sensorManager = getSensorManager(); |
+ private boolean registerForSensorType(int type, int rateInMilliseconds) { |
+ SensorManagerProxy sensorManager = getSensorManagerProxy(); |
if (sensorManager == null) { |
return false; |
} |
- List<Sensor> sensors = sensorManager.getSensorList(type); |
- if (sensors.isEmpty()) { |
- return false; |
+ final int rateInMicroseconds = 1000 * rateInMilliseconds; |
+ return sensorManager.registerListener(this, type, rateInMicroseconds, getHandler()); |
+ } |
+ |
+ protected void gotOrientation(double alpha, double beta, double gamma) { |
+ synchronized (mNativePtrLock) { |
+ if (mNativePtr != 0) { |
+ nativeGotOrientation(mNativePtr, alpha, beta, gamma); |
+ } |
} |
+ } |
- final int rateInMicroseconds = 1000 * rateInMilliseconds; |
- // We want to err on the side of getting more events than we need. |
- final int requestedRate = rateInMicroseconds / 2; |
+ protected void gotAcceleration(double x, double y, double z) { |
+ synchronized (mNativePtrLock) { |
+ if (mNativePtr != 0) { |
+ nativeGotAcceleration(mNativePtr, x, y, z); |
+ } |
+ } |
+ } |
- // TODO(steveblock): Consider handling multiple sensors. |
- return sensorManager.registerListener(this, sensors.get(0), requestedRate, getHandler()); |
+ protected void gotAccelerationIncludingGravity(double x, double y, double z) { |
+ synchronized (mNativePtrLock) { |
+ if (mNativePtr != 0) { |
+ nativeGotAccelerationIncludingGravity(mNativePtr, x, y, z); |
+ } |
+ } |
} |
- void gotOrientation(double alpha, double beta, double gamma) { |
+ protected void gotRotationRate(double alpha, double beta, double gamma) { |
synchronized (mNativePtrLock) { |
if (mNativePtr != 0) { |
- nativeGotOrientation(mNativePtr, alpha, beta, gamma); |
+ nativeGotRotationRate(mNativePtr, alpha, beta, gamma); |
} |
} |
} |
@@ -251,19 +394,79 @@ class DeviceOrientation implements SensorEventListener { |
} |
@CalledByNative |
- private static DeviceOrientation getInstance() { |
+ static DeviceMotionAndOrientation getInstance() { |
synchronized (sSingletonLock) { |
if (sSingleton == null) { |
- sSingleton = new DeviceOrientation(); |
+ sSingleton = new DeviceMotionAndOrientation(); |
} |
return sSingleton; |
} |
} |
/** |
- * See chrome/browser/device_orientation/data_fetcher_impl_android.cc |
+ * Native JNI calls, |
+ * see content/browser/device_orientation/data_fetcher_impl_android.cc |
+ */ |
+ |
+ /** |
+ * Orientation of the device with respect to its reference frame. |
*/ |
private native void nativeGotOrientation( |
int nativeDataFetcherImplAndroid, |
double alpha, double beta, double gamma); |
-} |
+ |
+ /** |
+ * Linear acceleration without gravity of the device with respect to its body frame. |
+ */ |
+ private native void nativeGotAcceleration( |
+ int nativeDataFetcherImplAndroid, |
+ double x, double y, double z); |
+ |
+ /** |
+ * Acceleration including gravity of the device with respect to its body frame. |
+ */ |
+ private native void nativeGotAccelerationIncludingGravity( |
+ int nativeDataFetcherImplAndroid, |
+ double x, double y, double z); |
+ |
+ /** |
+ * Rotation rate of the device with respect to its body frame. |
+ */ |
+ private native void nativeGotRotationRate( |
+ int nativeDataFetcherImplAndroid, |
+ double alpha, double beta, double gamma); |
+ |
+ /** |
+ * Need the an interface for SensorManager for testing. |
+ */ |
+ interface SensorManagerProxy { |
+ public boolean registerListener(SensorEventListener listener, int sensorType, int rate, |
+ Handler handler); |
+ public void unregisterListener(SensorEventListener listener, int sensorType); |
+ } |
+ |
+ static class SensorManagerProxyImpl implements SensorManagerProxy { |
+ private final SensorManager mSensorManager; |
+ |
+ SensorManagerProxyImpl(SensorManager sensorManager) { |
+ mSensorManager = sensorManager; |
+ } |
+ |
+ public boolean registerListener(SensorEventListener listener, int sensorType, int rate, |
+ Handler handler) { |
+ List<Sensor> sensors = mSensorManager.getSensorList(sensorType); |
+ if (sensors.isEmpty()) { |
+ return false; |
+ } |
+ return mSensorManager.registerListener(listener, sensors.get(0), rate, handler); |
+ } |
+ |
+ public void unregisterListener(SensorEventListener listener, int sensorType) { |
+ List<Sensor> sensors = mSensorManager.getSensorList(sensorType); |
+ if (!sensors.isEmpty()) { |
+ mSensorManager.unregisterListener(listener, sensors.get(0)); |
+ } |
+ } |
+ } |
+ |
+} |