Index: content/public/android/java/src/org/chromium/content/browser/DeviceMotionAndOrientation.java |
diff --git a/content/public/android/java/src/org/chromium/content/browser/DeviceMotionAndOrientation.java b/content/public/android/java/src/org/chromium/content/browser/DeviceMotionAndOrientation.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3dcdf253dedb7cf48883b0f92c7c420de15020cd |
--- /dev/null |
+++ b/content/public/android/java/src/org/chromium/content/browser/DeviceMotionAndOrientation.java |
@@ -0,0 +1,414 @@ |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
Peter Beverloo
2013/03/11 17:57:25
It's impossible to review changes in this file giv
timvolodine1
2013/03/12 11:18:06
Done.
|
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.content.browser; |
+ |
+import android.content.Context; |
+import android.hardware.Sensor; |
+import android.hardware.SensorEvent; |
+import android.hardware.SensorEventListener; |
+import android.hardware.SensorManager; |
+import android.os.Handler; |
+import android.os.Looper; |
+ |
+import com.google.common.collect.ImmutableMap; |
+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.Map; |
+import java.util.Set; |
+ |
+/** |
+ * Android implementation of the DeviceOrientation API. |
+ */ |
+@JNINamespace("content") |
+class DeviceMotionAndOrientation implements SensorEventListener { |
+ |
+ // These fields are lazily initialized by getHandler(). |
+ private Thread mThread; |
+ private Handler mHandler; |
+ |
+ // The lock to access the mHandler. |
+ private Object mHandlerLock = new Object(); |
+ |
+ // Non-zero if and only if we're listening for events. |
+ // To avoid race conditions on the C++ side, access must be synchronized. |
+ private int mNativePtr; |
+ |
+ // The lock to access the mNativePtr. |
+ private Object mNativePtrLock = new Object(); |
+ |
+ // The gravity vector expressed in the body frame. |
+ private float[] mGravityVector; |
+ |
+ // The geomagnetic vector expressed in the body frame. |
+ private float[] mMagneticFieldVector; |
+ |
+ // The linear acceleration vector expressed in the body frame. |
+ private float[] mAccelerationVector; |
+ |
+ // Gyroscope |
+ private float[] mGyroVector; |
+ |
+ // Lazily initialized when registering for notifications. |
+ private SensorManager mSensorManager; |
+ |
+ // The only instance of that class and its associated lock. |
+ private static DeviceMotionAndOrientation sSingleton; |
+ private static Object sSingletonLock = new Object(); |
+ |
+ /** |
+ * constants for using in JNI calls, also see |
+ * content/browser/device_orientation/data_fetcher_impl_android.cc |
+ * @see #start |
+ * @see #stop |
+ */ |
+ private static enum SpecEventType { |
+ DEVICE_ORIENTATION(0), |
+ DEVICE_MOTION(1); |
+ |
+ public int value; |
+ private SpecEventType(int value) { |
+ this.value = value; |
+ } |
+ } |
+ |
+ private static ImmutableSet<Integer> DEVICE_ORIENTATION_SENSORS = |
+ ImmutableSet.of(Sensor.TYPE_ACCELEROMETER, |
+ Sensor.TYPE_MAGNETIC_FIELD); |
+ |
+ private static ImmutableSet<Integer> DEVICE_MOTION_SENSORS = |
+ ImmutableSet.of(Sensor.TYPE_ACCELEROMETER, |
+ Sensor.TYPE_LINEAR_ACCELERATION, |
+ Sensor.TYPE_GYROSCOPE); |
+ |
+ private Set<Integer> mActiveSensors = Sets.newHashSet(); |
+ |
+ private static Map<Integer, ImmutableSet<Integer>> SENSORS_FOR_EVENT = ImmutableMap.of( |
+ SpecEventType.DEVICE_ORIENTATION.value, DEVICE_ORIENTATION_SENSORS, |
+ SpecEventType.DEVICE_MOTION.value, DEVICE_MOTION_SENSORS); |
+ |
+ // keep an array of active event types for speed because it is used in @see # |
+ private boolean[] mIsSpecEventActive = new boolean[SpecEventType.values().length]; |
+ |
+ private DeviceMotionAndOrientation() { |
+ mIsSpecEventActive[SpecEventType.DEVICE_ORIENTATION.value] = false; |
+ mIsSpecEventActive[SpecEventType.DEVICE_MOTION.value] = false; |
+ } |
+ |
+ /** |
+ * Start listening for sensor events. If this object is already listening |
+ * for events, the old callback is unregistered first. |
+ * |
+ * @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 specEventType type of event to listen to, can be 0 for device |
+ * orientation, and 1 for device motion. |
+ * @return True on success. |
+ */ |
+ @CalledByNative |
+ public boolean start(int nativePtr, int specEventType, int rateInMilliseconds) { |
+ Set<Integer> sensorsForEvent = SENSORS_FOR_EVENT.get(specEventType); |
+ if (sensorsForEvent == null) |
+ return false; |
+ |
+ synchronized (mNativePtrLock) { |
+ Set<Integer> sensorsToActivate = Sets.newHashSet(sensorsForEvent); |
+ sensorsToActivate.removeAll(mActiveSensors); |
+ boolean success = registerForSensors(sensorsToActivate, rateInMilliseconds); |
+ if (success) { |
+ mNativePtr = nativePtr; |
+ mIsSpecEventActive[specEventType] = true; |
+ } |
+ return success; |
+ } |
+ } |
+ |
+ /** |
+ * Stop listening for sensor events. Always succeeds. |
+ * |
+ * We strictly guarantee that nativeGotOrientation() will not be called |
+ * after this method returns. |
+ */ |
+ @CalledByNative |
+ public void stop(int specEventType) { |
+ Set<Integer> sensorsForEvent = SENSORS_FOR_EVENT.get(specEventType); |
+ if (sensorsForEvent == null) |
+ return; |
+ Set<Integer> sensorsToRemainActive = Sets.newHashSet(); |
+ |
+ synchronized (mNativePtrLock) { |
+ for (SpecEventType specEvent : SpecEventType.values()) { |
+ if (specEvent.value != specEventType && mIsSpecEventActive[specEvent.value]) { |
+ sensorsToRemainActive.addAll(SENSORS_FOR_EVENT.get(specEvent.value)); |
+ } |
+ } |
+ Set<Integer> sensorsToDeactivate = Sets.newHashSet(mActiveSensors); |
+ sensorsToDeactivate.removeAll(sensorsToRemainActive); |
+ unregisterForSensors(sensorsToDeactivate); |
+ mNativePtr = 0; |
+ mIsSpecEventActive[specEventType] = false; |
+ } |
+ } |
+ |
+ @Override |
+ public void onAccuracyChanged(Sensor sensor, int accuracy) { |
+ // Nothing |
+ } |
+ |
+ @Override |
+ public void onSensorChanged(SensorEvent event) { |
+ boolean dispatchDeviceOrientation = |
+ mIsSpecEventActive[SpecEventType.DEVICE_ORIENTATION.value]; |
+ boolean dispatchDeviceMotion = |
+ mIsSpecEventActive[SpecEventType.DEVICE_MOTION.value]; |
+ switch (event.sensor.getType()) { |
+ case Sensor.TYPE_ACCELEROMETER: |
+ if (mGravityVector == null) { |
+ mGravityVector = new float[3]; |
+ } |
+ System.arraycopy(event.values, 0, mGravityVector, 0, mGravityVector.length); |
+ if (dispatchDeviceMotion) |
+ gotAccelerationIncludingGravity(mGravityVector[0], mGravityVector[1], |
+ mGravityVector[2]); |
+ break; |
+ case Sensor.TYPE_LINEAR_ACCELERATION: |
+ if (mAccelerationVector == null) { |
+ mAccelerationVector = new float[3]; |
+ } |
+ System.arraycopy(event.values, 0, mAccelerationVector, 0, |
+ mAccelerationVector.length); |
+ if (dispatchDeviceMotion) |
+ gotAcceleration(mAccelerationVector[0], mAccelerationVector[1], |
+ mAccelerationVector[2]); |
+ break; |
+ |
+ case Sensor.TYPE_GYROSCOPE: |
+ if (mGyroVector == null) { |
+ mGyroVector = new float[3]; |
+ } |
+ System.arraycopy(event.values, 0, mGyroVector, 0, mGyroVector.length); |
+ if (dispatchDeviceMotion) |
+ gotRotationRate(mGyroVector[0], mGyroVector[1], mGyroVector[2]); |
+ break; |
+ |
+ case Sensor.TYPE_MAGNETIC_FIELD: |
+ if (mMagneticFieldVector == null) { |
+ mMagneticFieldVector = new float[3]; |
+ } |
+ System.arraycopy(event.values, 0, mMagneticFieldVector, 0, |
+ mMagneticFieldVector.length); |
+ break; |
+ |
+ default: |
+ // Unexpected |
+ return; |
+ } |
+ |
+ if (dispatchDeviceOrientation) |
+ getOrientationUsingGetRotationMatrix(); |
+ } |
+ |
+ void getOrientationUsingGetRotationMatrix() { |
+ if (mGravityVector == null || mMagneticFieldVector == null) { |
+ return; |
+ } |
+ |
+ // Get the rotation matrix. |
+ // The rotation matrix that transforms from the body frame to the earth |
+ // frame. |
+ float[] deviceRotationMatrix = new float[9]; |
+ if (!SensorManager.getRotationMatrix(deviceRotationMatrix, null, mGravityVector, |
+ mMagneticFieldVector)) { |
+ return; |
+ } |
+ |
+ // Convert rotation matrix to rotation angles. |
+ // Assuming that the rotations are appied in the order listed at |
+ // http://developer.android.com/reference/android/hardware/SensorEvent.html#values |
+ // the rotations are applied about the same axes and in the same order as required by the |
+ // API. The only conversions are sign changes as follows. The angles are in radians |
+ |
+ float[] rotationAngles = new float[3]; |
+ SensorManager.getOrientation(deviceRotationMatrix, rotationAngles); |
+ |
+ double alpha = Math.toDegrees(-rotationAngles[0]); |
+ while (alpha < 0.0) { |
+ alpha += 360.0; // [0, 360) |
+ } |
+ |
+ double beta = Math.toDegrees(-rotationAngles[1]); |
+ while (beta < -180.0) { |
+ beta += 360.0; // [-180, 180) |
+ } |
+ |
+ double gamma = Math.toDegrees(rotationAngles[2]); |
+ while (gamma < -90.0) { |
+ gamma += 360.0; // [-90, 90) |
+ } |
+ |
+ gotOrientation(alpha, beta, gamma); |
+ } |
+ |
+ private SensorManager getSensorManager() { |
+ if (mSensorManager != null) { |
+ return mSensorManager; |
+ } |
+ mSensorManager = (SensorManager)WeakContext.getSystemService(Context.SENSOR_SERVICE); |
+ return mSensorManager; |
+ } |
+ |
+ private boolean registerForSensors(Iterable<Integer> sensorTypes, int rateInMilliseconds) { |
+ for (Integer sensorType : sensorTypes) { |
+ if (!mActiveSensors.contains(sensorType)) { |
+ if (!registerForSensorType(sensorType, rateInMilliseconds)) { |
+ // restore the previous state upon failure |
+ unregisterForSensors(sensorTypes); |
+ return false; |
+ } |
+ } |
+ mActiveSensors.add(sensorType); |
+ } |
+ return true; |
+ } |
+ |
+ private void unregisterForSensors(Iterable<Integer> sensorTypes) { |
+ for (Integer sensorType : sensorTypes) { |
+ if (mActiveSensors.contains(sensorType)) { |
+ getSensorManager().unregisterListener(this, |
+ getSensorManager().getDefaultSensor(sensorType)); |
+ mActiveSensors.remove(sensorType); |
+ } |
+ } |
+ } |
+ |
+ boolean registerForSensorType(int type, int rateInMilliseconds) { |
+ SensorManager sensorManager = getSensorManager(); |
+ if (sensorManager == null) { |
+ return false; |
+ } |
+ Sensor defaultSensor = sensorManager.getDefaultSensor(type); |
+ if (defaultSensor == null) { |
+ return false; |
+ } |
+ |
+ final int rateInMicroseconds = 1000 * rateInMilliseconds; |
+ return sensorManager.registerListener(this, defaultSensor, rateInMicroseconds, |
+ getHandler()); |
+ } |
+ |
+ void gotOrientation(double alpha, double beta, double gamma) { |
+ synchronized (mNativePtrLock) { |
+ if (mNativePtr != 0) { |
+ nativeGotOrientation(mNativePtr, alpha, beta, gamma); |
+ } |
+ } |
+ } |
+ |
+ void gotAcceleration(double x, double y, double z) { |
+ synchronized (mNativePtrLock) { |
+ if (mNativePtr != 0) { |
+ nativeGotAcceleration(mNativePtr, x, y, z); |
+ } |
+ } |
+ } |
+ |
+ void gotAccelerationIncludingGravity(double x, double y, double z) { |
+ synchronized (mNativePtrLock) { |
+ if (mNativePtr != 0) { |
+ nativeGotAccelerationIncludingGravity(mNativePtr, x, y, z); |
+ } |
+ } |
+ } |
+ |
+ void gotRotationRate(double alpha, double beta, double gamma) { |
+ synchronized (mNativePtrLock) { |
+ if (mNativePtr != 0) { |
+ nativeGotRotationRate(mNativePtr, alpha, beta, gamma); |
+ } |
+ } |
+ } |
+ |
+ private Handler getHandler() { |
+ synchronized (mHandlerLock) { |
+ // If we don't have a background thread, start it now. |
+ if (mThread == null) { |
+ mThread = new Thread(new Runnable() { |
+ @Override |
+ public void run() { |
+ Looper.prepare(); |
+ // Our Handler doesn't actually have to do anything, because |
+ // SensorManager posts directly to the underlying Looper. |
+ setHandler(new Handler()); |
+ Looper.loop(); |
+ } |
+ }); |
+ mThread.start(); |
+ } |
+ // Wait for the background thread to spin up. |
+ while (mHandler == null) { |
+ try { |
+ mHandlerLock.wait(); |
+ } catch (InterruptedException e) { |
+ // Somebody doesn't want us to wait! That's okay, SensorManager accepts null. |
+ return null; |
+ } |
+ } |
+ return mHandler; |
+ } |
+ } |
+ |
+ private void setHandler(Handler handler) { |
+ synchronized (mHandlerLock) { |
+ mHandler = handler; |
+ mHandlerLock.notify(); |
+ } |
+ } |
+ |
+ @CalledByNative |
+ private static DeviceMotionAndOrientation getInstance() { |
+ synchronized (sSingletonLock) { |
+ if (sSingleton == null) { |
+ sSingleton = new DeviceMotionAndOrientation(); |
+ } |
+ return sSingleton; |
+ } |
+ } |
+ |
+ /** |
+ * Native JNI calls, |
+ * see content/browser/device_orientation/data_fetcher_impl_android.cc |
+ */ |
+ private native void nativeGotOrientation( |
+ int nativeDataFetcherImplAndroid, |
+ double alpha, double beta, double gamma); |
+ |
+ /** |
+ * linear acceleration w/o gravity |
+ */ |
+ private native void nativeGotAcceleration( |
+ int nativeDataFetcherImplAndroid, |
+ double x, double y, double z); |
+ |
+ /** |
+ * acceleration including gravity |
+ */ |
+ private native void nativeGotAccelerationIncludingGravity( |
+ int nativeDataFetcherImplAndroid, |
+ double x, double y, double z); |
+ |
+ /** |
+ * gyroscope |
+ */ |
+ private native void nativeGotRotationRate( |
+ int nativeDataFetcherImplAndroid, |
+ double alpha, double beta, double gamma); |
+ |
+} |